@mindexec/cli 0.2.494 → 0.2.496
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.
- package/electron/main.cjs +108 -7
- package/electron/native-renderer-owner-smoke.mjs +387 -0
- package/electron/native-renderer-owner.cjs +1044 -0
- package/electron/preload.cjs +12 -0
- package/electron/source-smoke.mjs +57 -0
- package/electron/windows-package-smoke.mjs +100 -0
- package/package.json +18 -8
- package/remote-fast/osx-arm64/mindexec-remote-fast.dll +0 -0
- package/remote-fast/osx-x64/mindexec-remote-fast.dll +0 -0
- package/remote-fast/win-x64/mindexec-remote-fast.dll +0 -0
- package/scripts/desktop-update-publisher-smoke.mjs +33 -5
- package/scripts/publish-mindexec-desktop-updates.mjs +34 -5
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +3 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-logic-workers.js +21 -1
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-native-render-mirror.js +586 -0
- package/wwwroot/_content/MindExecution.Shared/native-core/native-core-manifest.json +2 -2
- package/wwwroot/_framework/{MindExecution.Core.tre9kny2uo.dll → MindExecution.Core.s6sxjy1v7h.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Kernel.lvfnhwdksf.dll → MindExecution.Kernel.yta9wud6wh.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Admin.k2vbe4lt64.dll → MindExecution.Plugins.Admin.7aytj09ejo.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Business.u7i3r45xp5.dll → MindExecution.Plugins.Business.wzj1pi81by.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Concept.7eoasvev17.dll → MindExecution.Plugins.Concept.o6eyyttldg.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Directory.rhh13u0z6c.dll → MindExecution.Plugins.Directory.rkqhevehjj.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.xzl2x2cqk4.dll → MindExecution.Plugins.PlanMaster.qfmuwz1kj6.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.YouTube.945tf0zc9m.dll → MindExecution.Plugins.YouTube.p6hp1w5nfq.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Shared.e0w408b0ti.dll → MindExecution.Shared.b4arhy9bqr.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Web.vc6ux7xjcw.dll → MindExecution.Web.hlrui65yuu.dll} +0 -0
- package/wwwroot/_framework/blazor.boot.json +21 -21
- package/wwwroot/index.html +2 -1
- package/wwwroot/service-worker-assets.js +30 -26
- package/wwwroot/service-worker.js +1 -1
package/electron/preload.cjs
CHANGED
|
@@ -191,5 +191,17 @@ contextBridge.exposeInMainWorld('mindExecDesktop', {
|
|
|
191
191
|
ipcRenderer.on('updates:status', listener);
|
|
192
192
|
return () => ipcRenderer.removeListener('updates:status', listener);
|
|
193
193
|
}
|
|
194
|
+
},
|
|
195
|
+
nativeRender: {
|
|
196
|
+
getStatus: () => ipcRenderer.invoke('native-renderer:get-status'),
|
|
197
|
+
sendSnapshot: scene => ipcRenderer.invoke('native-renderer:snapshot', scene),
|
|
198
|
+
sendCamera: frame => ipcRenderer.invoke('native-renderer:camera', frame),
|
|
199
|
+
detach: owner => ipcRenderer.invoke('native-renderer:detach', owner),
|
|
200
|
+
onStatus: callback => {
|
|
201
|
+
if (typeof callback !== 'function') return () => undefined;
|
|
202
|
+
const listener = (_event, payload) => callback(payload);
|
|
203
|
+
ipcRenderer.on('native-renderer:status', listener);
|
|
204
|
+
return () => ipcRenderer.removeListener('native-renderer:status', listener);
|
|
205
|
+
}
|
|
194
206
|
}
|
|
195
207
|
});
|
|
@@ -6,12 +6,22 @@ import sharp from 'sharp';
|
|
|
6
6
|
|
|
7
7
|
const electronDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
8
8
|
const packageRoot = path.resolve(electronDirectory, '..');
|
|
9
|
+
const repositoryRoot = path.resolve(packageRoot, '..');
|
|
9
10
|
const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
10
11
|
const mainSource = fs.readFileSync(path.join(electronDirectory, 'main.cjs'), 'utf8');
|
|
11
12
|
const preloadSource = fs.readFileSync(path.join(electronDirectory, 'preload.cjs'), 'utf8');
|
|
13
|
+
const nativeRendererOwnerSource = fs.readFileSync(path.join(electronDirectory, 'native-renderer-owner.cjs'), 'utf8');
|
|
14
|
+
const nativeRendererOwnerSmokeSource = fs.readFileSync(
|
|
15
|
+
path.join(electronDirectory, 'native-renderer-owner-smoke.mjs'),
|
|
16
|
+
'utf8'
|
|
17
|
+
);
|
|
12
18
|
const updateManagerSource = fs.readFileSync(path.join(electronDirectory, 'update-manager.cjs'), 'utf8');
|
|
13
19
|
const updateFeed = JSON.parse(fs.readFileSync(path.join(electronDirectory, 'update-feed.json'), 'utf8'));
|
|
14
20
|
const serverSource = fs.readFileSync(path.join(packageRoot, 'server.js'), 'utf8');
|
|
21
|
+
const nativeRendererStageSource = fs.readFileSync(
|
|
22
|
+
path.join(repositoryRoot, 'scripts', 'prepare-desktop-native-renderer.ps1'),
|
|
23
|
+
'utf8'
|
|
24
|
+
);
|
|
15
25
|
|
|
16
26
|
assert.equal(packageJson.build?.appId, 'com.mindexec.desktop', 'Stable desktop appId is required.');
|
|
17
27
|
assert.equal(packageJson.build?.productName, 'MindExec', 'Desktop product name changed unexpectedly.');
|
|
@@ -35,7 +45,28 @@ assert.equal(packageJson.dependencies?.['electron-updater'], '^6.8.9', 'The pack
|
|
|
35
45
|
assert.equal(packageJson.build?.extraMetadata?.desktopUpdateChannel, 'dev', 'Development installers must stay on the dev feed.');
|
|
36
46
|
assert.equal(packageJson.build?.extraMetadata?.nativeCoreMode, 'native',
|
|
37
47
|
'Packaged development builds must use the validated native-primary core with bounded JS fallback.');
|
|
48
|
+
assert.equal(packageJson.build?.extraMetadata?.nativeRenderMode, 'mirror',
|
|
49
|
+
'Packaged Windows builds must start the native renderer in fail-open mirror mode.');
|
|
50
|
+
assert.match(mainSource, /app\.isPackaged[\s\S]*path\.resolve\(__dirname, '\.\.', 'package\.json'\)/,
|
|
51
|
+
'Source Electron must resolve nativeRenderMode from the LocalBridge package instead of the electron entry directory.');
|
|
52
|
+
assert.match(packageJson.scripts?.['desktop:build:win'] || '', /prepare-desktop-native-renderer\.ps1[\s\S]*electron-builder/,
|
|
53
|
+
'The Windows package build must stage the exact native renderer before Electron packaging.');
|
|
54
|
+
const nativeRendererResource = packageJson.build?.extraResources?.find(
|
|
55
|
+
resource => resource.to === 'native-renderer/windows-x64'
|
|
56
|
+
);
|
|
57
|
+
assert.equal(nativeRendererResource?.from, '../artifacts/desktop-native/windows-x64',
|
|
58
|
+
'Electron must package only the validated native renderer staging directory.');
|
|
59
|
+
assert.match(nativeRendererStageSource, /mindexec_windows_host\.exe/,
|
|
60
|
+
'The native renderer staging manifest must include the Windows host.');
|
|
61
|
+
assert.match(nativeRendererStageSource, /shaders\\dxbc\\vs_canvas_shape\.sc\.bin/,
|
|
62
|
+
'The native renderer staging manifest must include the Direct3D vertex shader.');
|
|
63
|
+
assert.match(nativeRendererStageSource, /shaders\\dxbc\\fs_canvas_shape\.sc\.bin/,
|
|
64
|
+
'The native renderer staging manifest must include the Direct3D fragment shader.');
|
|
65
|
+
assert.match(nativeRendererStageSource, /native-renderer-manifest\.json/,
|
|
66
|
+
'The staged native renderer must carry a reproducible manifest.');
|
|
38
67
|
assert.match(mainSource, /waitForNativeCoreSmoke/, 'Packaged smoke must verify the native Wasm runtime, not only its URL mode.');
|
|
68
|
+
assert.match(mainSource, /waitForNativeRendererSmoke[\s\S]*acceptedFrames/,
|
|
69
|
+
'Packaged smoke must wait for one real board snapshot accepted by the native renderer.');
|
|
39
70
|
assert.equal(packageJson.build?.publish?.provider, 'generic', 'Desktop updates must use the generic R2-backed provider.');
|
|
40
71
|
assert.equal(packageJson.build?.publish?.useMultipleRangeRequest, false,
|
|
41
72
|
'Packaged differential downloads must use one byte range per request.');
|
|
@@ -96,6 +127,13 @@ assert.match(mainSource, /createProductUpdateManager/, 'The Electron update mana
|
|
|
96
127
|
assert.match(mainSource, /updates:get-status/, 'The updater status IPC contract is missing.');
|
|
97
128
|
assert.match(mainSource, /updates:check/, 'The manual updater check IPC contract is missing.');
|
|
98
129
|
assert.match(mainSource, /updates:install/, 'The updater install IPC contract is missing.');
|
|
130
|
+
assert.match(mainSource, /createNativeRendererOwner/, 'The Electron shell must own the exact native renderer child.');
|
|
131
|
+
assert.match(mainSource, /native-renderer:get-status/, 'The native renderer status IPC contract is missing.');
|
|
132
|
+
assert.match(mainSource, /native-renderer:snapshot/, 'The native renderer snapshot IPC contract is missing.');
|
|
133
|
+
assert.match(mainSource, /native-renderer:camera/, 'The native renderer camera IPC contract is missing.');
|
|
134
|
+
assert.match(mainSource, /native-renderer:detach/, 'The native renderer detach IPC contract is missing.');
|
|
135
|
+
assert.match(mainSource, /await nativeRenderer\?\.stop\(\)[\s\S]{0,180}await stopBundledBridge\(\)/,
|
|
136
|
+
'The native renderer child must stop before an update installer starts.');
|
|
99
137
|
assert.match(mainSource, /await stopBundledBridge\(\)[\s\S]{0,180}await waitForBundledRuntimePortsReleased\(\)/,
|
|
100
138
|
'The Bridge and RemoteHub ports must reach zero before the installer starts.');
|
|
101
139
|
assert.doesNotMatch(mainSource, /Restart and update|promptForDownloadedUpdate|showMessageBox\(mainWindow/, 'Update availability must not open a restart modal.');
|
|
@@ -111,6 +149,25 @@ assert.match(preloadSource, /border:\s*0[\s\S]{0,80}border-radius:\s*50%[\s\S]{0
|
|
|
111
149
|
assert.match(preloadSource, /ipcRenderer\.invoke\('updates:install'\)/, 'Clicking the update indicator must use the owned install IPC path.');
|
|
112
150
|
assert.match(preloadSource, /mindExecDesktop/, 'The updater preload namespace changed unexpectedly.');
|
|
113
151
|
assert.match(preloadSource, /updates:\s*\{/, 'The updater preload API is missing.');
|
|
152
|
+
assert.match(preloadSource, /nativeRender:\s*\{/, 'The isolated native renderer preload API is missing.');
|
|
153
|
+
assert.match(preloadSource, /sendSnapshot:[\s\S]{0,100}native-renderer:snapshot/,
|
|
154
|
+
'The native renderer snapshot must cross the typed preload IPC boundary.');
|
|
155
|
+
assert.match(preloadSource, /sendCamera:[\s\S]{0,100}native-renderer:camera/,
|
|
156
|
+
'The native renderer camera must cross the typed preload IPC boundary.');
|
|
157
|
+
assert.match(nativeRendererOwnerSource, /MAX_PAYLOAD_BYTES\s*=\s*32\s*\*\s*1024\s*\*\s*1024/,
|
|
158
|
+
'The native renderer IPC payload must remain bounded to 32 MiB.');
|
|
159
|
+
assert.match(nativeRendererOwnerSource, /engine-generation-mismatch/,
|
|
160
|
+
'Electron must reject page messages from a stale native renderer generation.');
|
|
161
|
+
assert.match(nativeRendererOwnerSource, /LIFECYCLE_STATE\.stopping[\s\S]{0,180}native-renderer-stopping/,
|
|
162
|
+
'The native renderer owner must reject new work while its child is stopping.');
|
|
163
|
+
assert.match(nativeRendererOwnerSource, /native-renderer-force-exit-timeout/,
|
|
164
|
+
'A missing child exit confirmation must block native renderer shutdown completion.');
|
|
165
|
+
assert.match(nativeRendererOwnerSmokeSource, /sendOwned spawned a replacement native renderer/,
|
|
166
|
+
'The desktop suite must cover send-versus-stop lifecycle races.');
|
|
167
|
+
assert.match(nativeRendererOwnerSmokeSource, /stop promise settled when SIGKILL was sent/,
|
|
168
|
+
'The desktop suite must require a real child exit after force-kill.');
|
|
169
|
+
assert.match(nativeRendererOwnerSmokeSource, /force-exit timeout discarded the still-running child reference/,
|
|
170
|
+
'The desktop suite must retain ownership when a force-killed child never confirms exit.');
|
|
114
171
|
assert.match(updateManagerSource, /autoDownload\s*=\s*false/, 'Updates must not download through an unowned implicit path.');
|
|
115
172
|
assert.match(updateManagerSource, /autoInstallOnAppQuit\s*=\s*false/, 'Updates must not restart without the explicit prompt path.');
|
|
116
173
|
assert.match(updateManagerSource, /useMultipleRangeRequest:\s*false/,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
3
4
|
import fs from 'node:fs';
|
|
4
5
|
import net from 'node:net';
|
|
5
6
|
import os from 'node:os';
|
|
@@ -17,9 +18,68 @@ const outputDirectory = process.env.MINDEXEC_ELECTRON_OUTPUT
|
|
|
17
18
|
? path.resolve(process.env.MINDEXEC_ELECTRON_OUTPUT)
|
|
18
19
|
: path.join(repositoryRoot, 'artifacts', 'electron');
|
|
19
20
|
const executablePath = path.join(outputDirectory, 'win-unpacked', 'MindExec.exe');
|
|
21
|
+
const nativeRendererDirectory = path.join(
|
|
22
|
+
outputDirectory,
|
|
23
|
+
'win-unpacked',
|
|
24
|
+
'resources',
|
|
25
|
+
'native-renderer',
|
|
26
|
+
'windows-x64'
|
|
27
|
+
);
|
|
28
|
+
const nativeRendererExecutablePath = path.join(nativeRendererDirectory, 'mindexec_windows_host.exe');
|
|
29
|
+
const nativeRendererManifestPath = path.join(nativeRendererDirectory, 'native-renderer-manifest.json');
|
|
30
|
+
const nativeRendererVertexShaderPath = path.join(
|
|
31
|
+
nativeRendererDirectory,
|
|
32
|
+
'shaders',
|
|
33
|
+
'dxbc',
|
|
34
|
+
'vs_canvas_shape.sc.bin'
|
|
35
|
+
);
|
|
36
|
+
const nativeRendererFragmentShaderPath = path.join(
|
|
37
|
+
nativeRendererDirectory,
|
|
38
|
+
'shaders',
|
|
39
|
+
'dxbc',
|
|
40
|
+
'fs_canvas_shape.sc.bin'
|
|
41
|
+
);
|
|
20
42
|
|
|
21
43
|
assert.equal(process.platform, 'win32', 'The packaged Electron smoke test requires Windows.');
|
|
22
44
|
assert.ok(fs.existsSync(executablePath), `Packaged Electron executable is missing: ${executablePath}`);
|
|
45
|
+
assert.ok(fs.statSync(nativeRendererExecutablePath).size > 100_000,
|
|
46
|
+
`Packaged native renderer executable is missing or truncated: ${nativeRendererExecutablePath}`);
|
|
47
|
+
assert.ok(fs.statSync(nativeRendererVertexShaderPath).size > 100,
|
|
48
|
+
`Packaged native vertex shader is missing or truncated: ${nativeRendererVertexShaderPath}`);
|
|
49
|
+
assert.ok(fs.statSync(nativeRendererFragmentShaderPath).size > 100,
|
|
50
|
+
`Packaged native fragment shader is missing or truncated: ${nativeRendererFragmentShaderPath}`);
|
|
51
|
+
const nativeRendererManifest = JSON.parse(fs.readFileSync(nativeRendererManifestPath, 'utf8'));
|
|
52
|
+
assert.equal(nativeRendererManifest.schema, 'mindexec-native-renderer-package/v1',
|
|
53
|
+
'Packaged native renderer manifest schema drifted.');
|
|
54
|
+
assert.equal(nativeRendererManifest.msvcRuntime, 'static',
|
|
55
|
+
'Packaged native renderer must use the static Visual C++ runtime.');
|
|
56
|
+
assert.equal(nativeRendererManifest.requiresVisualCppRedistributable, false,
|
|
57
|
+
'Packaged native renderer must not require a separately installed Visual C++ Redistributable.');
|
|
58
|
+
assert.equal(nativeRendererManifest.nativeDesktopStage, 'composition-smoke',
|
|
59
|
+
'Packaged native renderer must identify the WebView2 composition-smoke stage.');
|
|
60
|
+
assert.equal(nativeRendererManifest.webView2Sdk, '1.0.3179.45',
|
|
61
|
+
'Packaged native renderer WebView2 SDK version drifted.');
|
|
62
|
+
assert.equal(nativeRendererManifest.webView2Loader, 'static',
|
|
63
|
+
'Packaged native renderer must statically link the WebView2 loader.');
|
|
64
|
+
assert.equal(nativeRendererManifest.requiresWebView2EvergreenRuntime, true,
|
|
65
|
+
'Packaged native renderer must declare the Evergreen WebView2 Runtime requirement.');
|
|
66
|
+
assert.deepEqual(
|
|
67
|
+
nativeRendererManifest.files.map(entry => entry.path).sort(),
|
|
68
|
+
[
|
|
69
|
+
'mindexec_windows_host.exe',
|
|
70
|
+
'shaders/dxbc/fs_canvas_shape.sc.bin',
|
|
71
|
+
'shaders/dxbc/vs_canvas_shape.sc.bin'
|
|
72
|
+
],
|
|
73
|
+
'Packaged native renderer manifest must name exactly the host and two Direct3D shaders.'
|
|
74
|
+
);
|
|
75
|
+
for (const entry of nativeRendererManifest.files) {
|
|
76
|
+
const packagedPath = path.join(nativeRendererDirectory, ...String(entry.path).split('/'));
|
|
77
|
+
const packagedBytes = fs.readFileSync(packagedPath);
|
|
78
|
+
assert.equal(packagedBytes.length, Number(entry.bytes),
|
|
79
|
+
`Packaged native renderer byte count drifted for ${entry.path}.`);
|
|
80
|
+
assert.equal(crypto.createHash('sha256').update(packagedBytes).digest('hex'), String(entry.sha256),
|
|
81
|
+
`Packaged native renderer SHA-256 drifted for ${entry.path}.`);
|
|
82
|
+
}
|
|
23
83
|
|
|
24
84
|
function reservePort() {
|
|
25
85
|
return new Promise((resolve, reject) => {
|
|
@@ -85,6 +145,25 @@ function canBind(port) {
|
|
|
85
145
|
});
|
|
86
146
|
}
|
|
87
147
|
|
|
148
|
+
function processExists(processId) {
|
|
149
|
+
if (!Number.isInteger(processId) || processId <= 0) return false;
|
|
150
|
+
try {
|
|
151
|
+
process.kill(processId, 0);
|
|
152
|
+
return true;
|
|
153
|
+
} catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function waitForProcessExit(processId, timeoutMs) {
|
|
159
|
+
const deadline = Date.now() + timeoutMs;
|
|
160
|
+
while (Date.now() < deadline) {
|
|
161
|
+
if (!processExists(processId)) return true;
|
|
162
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
163
|
+
}
|
|
164
|
+
return !processExists(processId);
|
|
165
|
+
}
|
|
166
|
+
|
|
88
167
|
const bridgePort = await reservePort();
|
|
89
168
|
const remoteHubPort = await reservePort();
|
|
90
169
|
const smokeRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'mindexec-electron-smoke-'));
|
|
@@ -142,6 +221,22 @@ try {
|
|
|
142
221
|
assert.match(result.nativeCore?.lastPrimaryResultType || '', /^(?:VISIBILITY|HIT_TEST|SELECTION_QUERY)_RESULT$/,
|
|
143
222
|
'Packaged Electron did not report the accepted native-core result type.');
|
|
144
223
|
assert.equal(result.nativeCore?.lastError, '', 'Packaged Electron native core reported an initialization error.');
|
|
224
|
+
assert.equal(result.nativeRenderer?.mode, 'mirror',
|
|
225
|
+
'Packaged Electron did not enable the native renderer mirror.');
|
|
226
|
+
assert.equal(result.nativeRenderer?.ready, true,
|
|
227
|
+
`Packaged native renderer did not become ready: ${result.nativeRenderer?.lastError || 'unknown error'}`);
|
|
228
|
+
assert.ok(String(result.nativeRenderer?.backend || '').length > 0,
|
|
229
|
+
'Packaged native renderer did not report its bgfx backend.');
|
|
230
|
+
assert.ok(Number(result.nativeRenderer?.acceptedFrames || 0) > 0,
|
|
231
|
+
'Packaged native renderer became ready but did not accept a real board frame.');
|
|
232
|
+
assert.equal(Number(result.nativeRenderer?.rejectedFrames || 0), 0,
|
|
233
|
+
'Packaged native renderer rejected a frame during its initial board mirror smoke.');
|
|
234
|
+
assert.ok(Number(result.nativeRenderer?.engineGeneration || 0) > 0,
|
|
235
|
+
'Packaged native renderer did not report an owned engine generation.');
|
|
236
|
+
assert.ok(Number(result.nativeRenderer?.processId || 0) > 0,
|
|
237
|
+
'Packaged native renderer process id is missing.');
|
|
238
|
+
assert.equal(result.nativeRenderer?.lastError, '',
|
|
239
|
+
'Packaged native renderer reported an initialization error.');
|
|
145
240
|
assert.equal(result.windowTitle, `MindExec ${packageJson.version}`,
|
|
146
241
|
'Packaged Electron did not keep the version visible in the title bar.');
|
|
147
242
|
assert.equal(result.bridgePort, bridgePort, 'Packaged shell did not use the requested Bridge port.');
|
|
@@ -154,6 +249,8 @@ try {
|
|
|
154
249
|
assert.equal(exitCode, 0, `Packaged Electron smoke exited with code ${exitCode}.`);
|
|
155
250
|
|
|
156
251
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
252
|
+
assert.equal(await waitForProcessExit(Number(result.nativeRenderer.processId), 5_000), true,
|
|
253
|
+
`Native renderer process ${result.nativeRenderer.processId} survived Electron exit.`);
|
|
157
254
|
assert.equal(await canBind(bridgePort), true, 'Bridge port was not released after Electron exit.');
|
|
158
255
|
assert.equal(await canBind(remoteHubPort), true, 'RemoteHub port was not released after Electron exit.');
|
|
159
256
|
|
|
@@ -162,6 +259,9 @@ try {
|
|
|
162
259
|
executablePath,
|
|
163
260
|
version: result.appVersion,
|
|
164
261
|
runtimeMode: result.runtimeMode,
|
|
262
|
+
nativeRendererBackend: result.nativeRenderer.backend,
|
|
263
|
+
nativeRendererAcceptedFrames: result.nativeRenderer.acceptedFrames,
|
|
264
|
+
nativeRendererProcessReleased: true,
|
|
165
265
|
desktopUpdateKind: result.desktopUpdate.installKind,
|
|
166
266
|
bridgePortReleased: true,
|
|
167
267
|
remoteHubPortReleased: true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mindexec/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.496",
|
|
4
4
|
"description": "MindExec local runtime and bridge CLI",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"type": "module",
|
|
@@ -28,15 +28,15 @@
|
|
|
28
28
|
"start": "node launch-bridge.cjs",
|
|
29
29
|
"dev": "node launch-bridge.cjs --watch",
|
|
30
30
|
"desktop:start": "electron electron/main.cjs",
|
|
31
|
-
"desktop:build:win": "electron-builder --win --x64 --publish never",
|
|
31
|
+
"desktop:build:win": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File ../scripts/prepare-desktop-native-renderer.ps1 && electron-builder --win --x64 --publish never",
|
|
32
32
|
"desktop:inspect:update:dev": "node scripts/publish-mindexec-desktop-updates.mjs --channel dev --platform windows-x64 --dir ../artifacts/electron --allow-unsigned",
|
|
33
33
|
"desktop:publish:update:dev": "node scripts/publish-mindexec-desktop-updates.mjs --channel dev --platform windows-x64 --dir ../artifacts/electron --allow-unsigned --publish",
|
|
34
34
|
"desktop:deploy:update-worker": "node ../r2-auth-proxy/node_modules/wrangler/wrangler-dist/cli.js deploy --config ../workers/mindexec-desktop-updates/wrangler.jsonc",
|
|
35
35
|
"desktop:check:update-worker": "node ../r2-auth-proxy/node_modules/wrangler/wrangler-dist/cli.js deploy --dry-run --config ../workers/mindexec-desktop-updates/wrangler.jsonc",
|
|
36
36
|
"test:desktop:update": "node --test electron/update-manager-smoke.mjs electron/recurring-update-owner-smoke.mjs scripts/desktop-update-worker-smoke.mjs scripts/desktop-update-publisher-smoke.mjs",
|
|
37
|
-
"test:desktop": "node electron/source-smoke.mjs && node scripts/desktop-workspace-state-smoke.mjs && npm run test:desktop:update",
|
|
37
|
+
"test:desktop": "node electron/source-smoke.mjs && node electron/native-renderer-owner-smoke.mjs && node scripts/desktop-workspace-state-smoke.mjs && npm run test:desktop:update",
|
|
38
38
|
"test:desktop:win": "node electron/windows-package-smoke.mjs",
|
|
39
|
-
"test:syntax": "node --check server.js && node --check remote-hub.js && node --check lzo1x.js && node --check mode4-atlas.js && node --check mode4-atlas-worker.js && node --check codex-runtime.js && node --check codex-model-catalog.js && node --check launch-bridge.cjs && node --check port-guard.cjs && node --check desktop-workspace-state.cjs && node --check electron/main.cjs && node --check electron/preload.cjs && node --check electron/update-manager.cjs && node --check electron/update-feed-resolver.cjs && node --check electron/recurring-update-owner.cjs && node --check electron/source-smoke.mjs && node --check electron/windows-package-smoke.mjs && node --check electron/update-manager-smoke.mjs && node --check electron/recurring-update-owner-smoke.mjs && node --check scripts/desktop-workspace-state-smoke.mjs && node --check scripts/desktop-update-worker-smoke.mjs && node --check scripts/desktop-update-publisher-smoke.mjs && node --check scripts/board-render-ownership-smoke.mjs && node --check ../workers/mindexec-desktop-updates/src/index.mjs && node --check scripts/setup-tree-sitter-grammars.mjs && node --check scripts/npm-fresh-install-smoke.mjs && node --check scripts/auth-session-smoke.mjs && node --check scripts/codex-model-catalog-smoke.mjs && node --check scripts/codex-sdk-runtime-smoke.mjs && node --check scripts/codex-sdk-image-runtime-smoke.mjs && node --check scripts/codex-sdk-image-live-smoke.mjs && node --check scripts/codex-runtime-home-smoke.mjs && node --check scripts/remote-hub-smoke.mjs && node --check scripts/remote-hub-scale-smoke.mjs && node --check scripts/remote-fleet-render-smoke.mjs && node --check scripts/remote-http-smoke.mjs && node --check scripts/remote-frame-ws-smoke.mjs && node --check scripts/remote-input-ws-smoke.mjs && node --check scripts/remote-agent-ws-smoke.mjs && node --check scripts/remote-agent-managed-smoke.mjs && node --check scripts/remote-registry-follower-smoke.mjs && node --check scripts/remote-agent-package-smoke.mjs && node --check scripts/remote-fast-live-rate-smoke.mjs && node --check scripts/remote-fast-mdm-browser-smoke.mjs",
|
|
39
|
+
"test:syntax": "node --check server.js && node --check remote-hub.js && node --check lzo1x.js && node --check mode4-atlas.js && node --check mode4-atlas-worker.js && node --check codex-runtime.js && node --check codex-model-catalog.js && node --check launch-bridge.cjs && node --check port-guard.cjs && node --check desktop-workspace-state.cjs && node --check electron/main.cjs && node --check electron/preload.cjs && node --check electron/native-renderer-owner.cjs && node --check electron/update-manager.cjs && node --check electron/update-feed-resolver.cjs && node --check electron/recurring-update-owner.cjs && node --check electron/source-smoke.mjs && node --check electron/native-renderer-owner-smoke.mjs && node --check electron/windows-package-smoke.mjs && node --check electron/update-manager-smoke.mjs && node --check electron/recurring-update-owner-smoke.mjs && node --check scripts/desktop-workspace-state-smoke.mjs && node --check scripts/desktop-update-worker-smoke.mjs && node --check scripts/desktop-update-publisher-smoke.mjs && node --check scripts/board-render-ownership-smoke.mjs && node --check ../workers/mindexec-desktop-updates/src/index.mjs && node --check scripts/setup-tree-sitter-grammars.mjs && node --check scripts/npm-fresh-install-smoke.mjs && node --check scripts/auth-session-smoke.mjs && node --check scripts/codex-model-catalog-smoke.mjs && node --check scripts/codex-sdk-runtime-smoke.mjs && node --check scripts/codex-sdk-image-runtime-smoke.mjs && node --check scripts/codex-sdk-image-live-smoke.mjs && node --check scripts/codex-runtime-home-smoke.mjs && node --check scripts/remote-hub-smoke.mjs && node --check scripts/remote-hub-scale-smoke.mjs && node --check scripts/remote-fleet-render-smoke.mjs && node --check scripts/remote-http-smoke.mjs && node --check scripts/remote-frame-ws-smoke.mjs && node --check scripts/remote-input-ws-smoke.mjs && node --check scripts/remote-agent-ws-smoke.mjs && node --check scripts/remote-agent-managed-smoke.mjs && node --check scripts/remote-registry-follower-smoke.mjs && node --check scripts/remote-agent-package-smoke.mjs && node --check scripts/remote-fast-live-rate-smoke.mjs && node --check scripts/remote-fast-mdm-browser-smoke.mjs",
|
|
40
40
|
"test:canvas:board-owner": "node scripts/board-render-ownership-smoke.mjs",
|
|
41
41
|
"test:codex-sdk": "node scripts/codex-sdk-runtime-smoke.mjs",
|
|
42
42
|
"test:codex-sdk:image": "node scripts/codex-sdk-image-runtime-smoke.mjs",
|
|
@@ -115,12 +115,13 @@
|
|
|
115
115
|
"extraMetadata": {
|
|
116
116
|
"main": "electron/main.cjs",
|
|
117
117
|
"desktopUpdateChannel": "dev",
|
|
118
|
-
"nativeCoreMode": "native"
|
|
118
|
+
"nativeCoreMode": "native",
|
|
119
|
+
"nativeRenderMode": "mirror"
|
|
119
120
|
},
|
|
120
121
|
"directories": {
|
|
121
122
|
"output": "../artifacts/electron"
|
|
122
123
|
},
|
|
123
|
-
"files": [
|
|
124
|
+
"files": [
|
|
124
125
|
"*.js",
|
|
125
126
|
"*.cjs",
|
|
126
127
|
"*.json",
|
|
@@ -129,8 +130,17 @@
|
|
|
129
130
|
"scripts/**/*",
|
|
130
131
|
"tree-sitter-grammars/**/*",
|
|
131
132
|
"wwwroot/**/*",
|
|
132
|
-
"node_modules/**/*"
|
|
133
|
-
],
|
|
133
|
+
"node_modules/**/*"
|
|
134
|
+
],
|
|
135
|
+
"extraResources": [
|
|
136
|
+
{
|
|
137
|
+
"from": "../artifacts/desktop-native/windows-x64",
|
|
138
|
+
"to": "native-renderer/windows-x64",
|
|
139
|
+
"filter": [
|
|
140
|
+
"**/*"
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
],
|
|
134
144
|
"win": {
|
|
135
145
|
"icon": "../MindExecution.Web/wwwroot/app-icon-1024.png",
|
|
136
146
|
"executableName": "MindExec",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -3,6 +3,7 @@ import { createHash } from 'node:crypto';
|
|
|
3
3
|
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
6
7
|
import test from 'node:test';
|
|
7
8
|
import {
|
|
8
9
|
createWranglerPutCommand,
|
|
@@ -10,6 +11,9 @@ import {
|
|
|
10
11
|
readReleasePlan
|
|
11
12
|
} from './publish-mindexec-desktop-updates.mjs';
|
|
12
13
|
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
const { buildBlockMap } = require('app-builder-lib/out/targets/blockmap/blockmap.js');
|
|
16
|
+
|
|
13
17
|
test('publisher launches Wrangler directly and targets the private update bucket', () => {
|
|
14
18
|
const command = createWranglerPutCommand({
|
|
15
19
|
key: 'dev/windows/x64/MindExec-0.2.461-dev-x64.exe',
|
|
@@ -45,7 +49,7 @@ test('publisher validates version, size, and SHA-512 before upload', async () =>
|
|
|
45
49
|
const parsed = parseUpdateManifest(await (await import('node:fs/promises')).readFile(join(directory, 'latest.yml'), 'utf8'));
|
|
46
50
|
assert.equal(parsed.version, '1.2.3');
|
|
47
51
|
assert.equal(parsed.files[0].name, artifact);
|
|
48
|
-
assert.
|
|
52
|
+
await assert.rejects(
|
|
49
53
|
() => readReleasePlan({
|
|
50
54
|
platform: 'windows-x64',
|
|
51
55
|
directory,
|
|
@@ -54,7 +58,7 @@ test('publisher validates version, size, and SHA-512 before upload', async () =>
|
|
|
54
58
|
}),
|
|
55
59
|
/desktop-update-dev-unsigned-ack-required/
|
|
56
60
|
);
|
|
57
|
-
assert.
|
|
61
|
+
await assert.rejects(
|
|
58
62
|
() => readReleasePlan({
|
|
59
63
|
platform: 'windows-x64',
|
|
60
64
|
directory,
|
|
@@ -65,7 +69,31 @@ test('publisher validates version, size, and SHA-512 before upload', async () =>
|
|
|
65
69
|
/desktop-update-blockmap-missing/
|
|
66
70
|
);
|
|
67
71
|
await writeFile(join(directory, `${artifact}.blockmap`), Buffer.from('blockmap'));
|
|
68
|
-
|
|
72
|
+
await assert.rejects(
|
|
73
|
+
() => readReleasePlan({
|
|
74
|
+
platform: 'windows-x64',
|
|
75
|
+
directory,
|
|
76
|
+
packageVersion: '1.2.3',
|
|
77
|
+
channel: 'dev',
|
|
78
|
+
allowUnsigned: true
|
|
79
|
+
}),
|
|
80
|
+
/desktop-update-blockmap-artifact-mismatch/
|
|
81
|
+
);
|
|
82
|
+
const differentArtifactPath = join(directory, 'different-installer.exe');
|
|
83
|
+
await writeFile(differentArtifactPath, Buffer.from('different-installer-bytes'));
|
|
84
|
+
await buildBlockMap(differentArtifactPath, 'gzip', join(directory, `${artifact}.blockmap`));
|
|
85
|
+
await assert.rejects(
|
|
86
|
+
() => readReleasePlan({
|
|
87
|
+
platform: 'windows-x64',
|
|
88
|
+
directory,
|
|
89
|
+
packageVersion: '1.2.3',
|
|
90
|
+
channel: 'dev',
|
|
91
|
+
allowUnsigned: true
|
|
92
|
+
}),
|
|
93
|
+
/desktop-update-blockmap-artifact-mismatch/
|
|
94
|
+
);
|
|
95
|
+
await buildBlockMap(join(directory, artifact), 'gzip', join(directory, `${artifact}.blockmap`));
|
|
96
|
+
const plan = await readReleasePlan({
|
|
69
97
|
platform: 'windows-x64',
|
|
70
98
|
directory,
|
|
71
99
|
packageVersion: '1.2.3',
|
|
@@ -74,7 +102,7 @@ test('publisher validates version, size, and SHA-512 before upload', async () =>
|
|
|
74
102
|
});
|
|
75
103
|
assert.equal(plan.prefix, 'dev/windows/x64');
|
|
76
104
|
assert.deepEqual(plan.artifacts.map(item => item.fileName), [artifact, `${artifact}.blockmap`]);
|
|
77
|
-
assert.
|
|
105
|
+
await assert.rejects(
|
|
78
106
|
() => readReleasePlan({
|
|
79
107
|
platform: 'windows-x64',
|
|
80
108
|
directory,
|
|
@@ -84,7 +112,7 @@ test('publisher validates version, size, and SHA-512 before upload', async () =>
|
|
|
84
112
|
}),
|
|
85
113
|
/desktop-update-version-mismatch/
|
|
86
114
|
);
|
|
87
|
-
assert.
|
|
115
|
+
await assert.rejects(
|
|
88
116
|
() => readReleasePlan({
|
|
89
117
|
platform: 'windows-x64',
|
|
90
118
|
directory,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
3
4
|
import { basename, isAbsolute, join, resolve, sep } from 'node:path';
|
|
4
5
|
import { pathToFileURL } from 'node:url';
|
|
5
6
|
import { spawnSync } from 'node:child_process';
|
|
@@ -14,6 +15,7 @@ const PLATFORM_CONFIG = {
|
|
|
14
15
|
const packageRoot = resolve(import.meta.dirname, '..');
|
|
15
16
|
const repositoryRoot = resolve(packageRoot, '..');
|
|
16
17
|
const require = createRequire(import.meta.url);
|
|
18
|
+
const { buildBlockMap } = require('app-builder-lib/out/targets/blockmap/blockmap.js');
|
|
17
19
|
|
|
18
20
|
function resolveWranglerEntry() {
|
|
19
21
|
const explicit = String(process.env.MINDEXEC_WRANGLER_ENTRY || '').trim();
|
|
@@ -51,9 +53,25 @@ function sha512Base64(filePath) {
|
|
|
51
53
|
function contentType(fileName) {
|
|
52
54
|
if (/\.ya?ml$/i.test(fileName)) return 'text/yaml; charset=utf-8';
|
|
53
55
|
if (/\.json$/i.test(fileName)) return 'application/json; charset=utf-8';
|
|
56
|
+
if (/\.blockmap$/i.test(fileName)) return 'application/gzip';
|
|
54
57
|
return 'application/octet-stream';
|
|
55
58
|
}
|
|
56
59
|
|
|
60
|
+
async function assertBlockmapMatchesArtifact(artifactPath, blockmapPath) {
|
|
61
|
+
const scratchDirectory = mkdtempSync(join(tmpdir(), 'mindexec-blockmap-verify-'));
|
|
62
|
+
const expectedBlockmapPath = join(scratchDirectory, 'expected.blockmap');
|
|
63
|
+
try {
|
|
64
|
+
await buildBlockMap(artifactPath, 'gzip', expectedBlockmapPath);
|
|
65
|
+
const expected = readFileSync(expectedBlockmapPath);
|
|
66
|
+
const actual = readFileSync(blockmapPath);
|
|
67
|
+
if (!actual.equals(expected)) {
|
|
68
|
+
throw new Error(`desktop-update-blockmap-artifact-mismatch:${basename(blockmapPath)}`);
|
|
69
|
+
}
|
|
70
|
+
} finally {
|
|
71
|
+
rmSync(scratchDirectory, { recursive: true, force: true });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
57
75
|
function isReleaseArtifact(fileName, version, channel) {
|
|
58
76
|
const prefix = channel === 'dev'
|
|
59
77
|
? `MindExec-${version}-dev-`
|
|
@@ -61,7 +79,7 @@ function isReleaseArtifact(fileName, version, channel) {
|
|
|
61
79
|
return fileName.startsWith(prefix) && /(?:\.exe|\.exe\.blockmap)$/i.test(fileName);
|
|
62
80
|
}
|
|
63
81
|
|
|
64
|
-
export function readReleasePlan({
|
|
82
|
+
export async function readReleasePlan({
|
|
65
83
|
platform,
|
|
66
84
|
directory,
|
|
67
85
|
packageVersion,
|
|
@@ -102,9 +120,11 @@ export function readReleasePlan({
|
|
|
102
120
|
}
|
|
103
121
|
if (/\.exe$/i.test(item.name)) {
|
|
104
122
|
const blockmapName = `${item.name}.blockmap`;
|
|
105
|
-
|
|
123
|
+
const blockmapPath = resolve(releaseDir, blockmapName);
|
|
124
|
+
if (!existsSync(blockmapPath)) {
|
|
106
125
|
throw new Error(`desktop-update-blockmap-missing:${blockmapName}`);
|
|
107
126
|
}
|
|
127
|
+
await assertBlockmapMatchesArtifact(filePath, blockmapPath);
|
|
108
128
|
}
|
|
109
129
|
}
|
|
110
130
|
|
|
@@ -171,8 +191,9 @@ async function verifyPublished(plan) {
|
|
|
171
191
|
}
|
|
172
192
|
|
|
173
193
|
for (const artifact of plan.artifacts) {
|
|
194
|
+
const verifyBody = /\.blockmap$/i.test(artifact.fileName);
|
|
174
195
|
const artifactResponse = await fetch(`${PUBLIC_BASE_URL}/${plan.prefix}/${encodeURIComponent(artifact.fileName)}`, {
|
|
175
|
-
method: 'HEAD',
|
|
196
|
+
method: verifyBody ? 'GET' : 'HEAD',
|
|
176
197
|
cache: 'no-store'
|
|
177
198
|
});
|
|
178
199
|
if (!artifactResponse.ok) {
|
|
@@ -182,6 +203,14 @@ async function verifyPublished(plan) {
|
|
|
182
203
|
if (Number(artifactResponse.headers.get('content-length') || 0) !== expectedSize) {
|
|
183
204
|
throw new Error(`desktop-update-public-artifact-size-mismatch:${artifact.fileName}`);
|
|
184
205
|
}
|
|
206
|
+
if (verifyBody) {
|
|
207
|
+
const publishedHash = createHash('sha512')
|
|
208
|
+
.update(Buffer.from(await artifactResponse.arrayBuffer()))
|
|
209
|
+
.digest('base64');
|
|
210
|
+
if (publishedHash !== sha512Base64(artifact.filePath)) {
|
|
211
|
+
throw new Error(`desktop-update-public-artifact-hash-mismatch:${artifact.fileName}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
185
214
|
}
|
|
186
215
|
}
|
|
187
216
|
|
|
@@ -199,7 +228,7 @@ async function main() {
|
|
|
199
228
|
}
|
|
200
229
|
|
|
201
230
|
const packageInfo = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'));
|
|
202
|
-
const plan = readReleasePlan({ platform, directory, packageVersion: packageInfo.version, channel, allowUnsigned });
|
|
231
|
+
const plan = await readReleasePlan({ platform, directory, packageVersion: packageInfo.version, channel, allowUnsigned });
|
|
203
232
|
const publish = hasFlag('--publish');
|
|
204
233
|
console.log(JSON.stringify({
|
|
205
234
|
platform: plan.platform,
|
|
@@ -2997,6 +2997,9 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
|
|
|
2997
2997
|
boardId,
|
|
2998
2998
|
reason: 'cache-attach'
|
|
2999
2999
|
});
|
|
3000
|
+
// A JS cache hit skips the C# OnBoardLoaded path, so its normal
|
|
3001
|
+
// completion callback cannot restore this active-board marker.
|
|
3002
|
+
markBoardLoadComplete(boardId, 'cache-attach');
|
|
3000
3003
|
|
|
3001
3004
|
// Force a few render frames
|
|
3002
3005
|
moduleInstance._forceUpdateFrames = Math.max(moduleInstance._forceUpdateFrames || 0, 6);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const LOGIC_WORKER_BRIDGE_BUILD_ID = '20260830-native-
|
|
4
|
+
const LOGIC_WORKER_BRIDGE_BUILD_ID = '20260830-native-render-mirror-v014';
|
|
5
5
|
const LEGACY_VISIBILITY_WORKER_URL = `_content/MindExecution.Shared/js/mind-map-visibility-worker.js?v=${LOGIC_WORKER_BRIDGE_BUILD_ID}`;
|
|
6
6
|
const NATIVE_CORE_MODE = String(window.MindMapNativeCoreBridge?.getMode?.() || 'off');
|
|
7
7
|
const NATIVE_PRIMARY_ENABLED = NATIVE_CORE_MODE === 'native';
|
|
@@ -245,6 +245,7 @@
|
|
|
245
245
|
this._pendingSelectionResolvers = new Map();
|
|
246
246
|
this._pendingSearchResolvers = new Map();
|
|
247
247
|
this._nativeCoreShadow = window.MindMapNativeCoreBridge?.createShadow?.() || null;
|
|
248
|
+
this._nativeRenderMirror = window.MindMapNativeRenderMirror?.createController?.(this.module) || null;
|
|
248
249
|
|
|
249
250
|
this._createWorkers();
|
|
250
251
|
}
|
|
@@ -1151,6 +1152,8 @@
|
|
|
1151
1152
|
|
|
1152
1153
|
dispose() {
|
|
1153
1154
|
this._disposed = true;
|
|
1155
|
+
this._nativeRenderMirror?.dispose?.();
|
|
1156
|
+
this._nativeRenderMirror = null;
|
|
1154
1157
|
this._clearVisibilityReadyTimer();
|
|
1155
1158
|
this._resetPendingVisibilityQueries();
|
|
1156
1159
|
try {
|
|
@@ -1200,6 +1203,7 @@
|
|
|
1200
1203
|
reason,
|
|
1201
1204
|
cellSize: Math.max(1, Number(this.module?.GRID_CELL_SIZE || 500))
|
|
1202
1205
|
};
|
|
1206
|
+
this._nativeRenderMirror?.resetBoard?.(normalizedBoardId, reason);
|
|
1203
1207
|
this._postVisibility(payload);
|
|
1204
1208
|
this._post(this._lodPlanWorker, payload);
|
|
1205
1209
|
this._postSearch(payload);
|
|
@@ -1237,6 +1241,11 @@
|
|
|
1237
1241
|
cellSize: Math.max(1, Number(activeModule.GRID_CELL_SIZE || 500)),
|
|
1238
1242
|
nodes
|
|
1239
1243
|
};
|
|
1244
|
+
this._nativeRenderMirror?.geometryChanged?.(
|
|
1245
|
+
activeModule,
|
|
1246
|
+
boardId,
|
|
1247
|
+
String(options.reason || 'rebuild-node-tables')
|
|
1248
|
+
);
|
|
1240
1249
|
|
|
1241
1250
|
const postedVisibility = this._postVisibility(payload);
|
|
1242
1251
|
const postedLodPlan = this._post(this._lodPlanWorker, payload);
|
|
@@ -1278,6 +1287,11 @@
|
|
|
1278
1287
|
this._boardRevision++;
|
|
1279
1288
|
this._visibilityNodeIds.add(snapshot.id);
|
|
1280
1289
|
this._visibilityNodeSnapshots.set(snapshot.id, snapshot);
|
|
1290
|
+
this._nativeRenderMirror?.geometryChanged?.(
|
|
1291
|
+
activeModule,
|
|
1292
|
+
boardId,
|
|
1293
|
+
String(options.reason || 'upsert-node')
|
|
1294
|
+
);
|
|
1281
1295
|
}
|
|
1282
1296
|
if (searchSnapshot) {
|
|
1283
1297
|
this._searchRevision++;
|
|
@@ -1318,6 +1332,11 @@
|
|
|
1318
1332
|
this._searchRevision++;
|
|
1319
1333
|
this._visibilityNodeIds.delete(normalizedNodeId);
|
|
1320
1334
|
this._visibilityNodeSnapshots.delete(normalizedNodeId);
|
|
1335
|
+
this._nativeRenderMirror?.geometryChanged?.(
|
|
1336
|
+
module || this.module,
|
|
1337
|
+
boardId,
|
|
1338
|
+
String(options.reason || 'remove-node')
|
|
1339
|
+
);
|
|
1321
1340
|
|
|
1322
1341
|
const payload = {
|
|
1323
1342
|
type: 'REMOVE_NODE',
|
|
@@ -1364,6 +1383,7 @@
|
|
|
1364
1383
|
}
|
|
1365
1384
|
|
|
1366
1385
|
requestVisibility(module = this.module, camera) {
|
|
1386
|
+
this._nativeRenderMirror?.cameraChanged?.(module || this.module, camera);
|
|
1367
1387
|
if (this._visibilityDisabled || !this._visibilityReady) {
|
|
1368
1388
|
return false;
|
|
1369
1389
|
}
|