@mindexec/cli 0.2.483 → 0.2.494

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 (70) hide show
  1. package/codex-runtime.js +6 -2
  2. package/electron/main.cjs +56 -32
  3. package/electron/preload.cjs +178 -0
  4. package/electron/source-smoke.mjs +14 -1
  5. package/electron/windows-package-smoke.mjs +16 -0
  6. package/package.json +11 -10
  7. package/remote-fast/osx-arm64/mindexec-remote-fast.dll +0 -0
  8. package/remote-fast/osx-x64/mindexec-remote-fast.dll +0 -0
  9. package/remote-fast/win-x64/mindexec-remote-fast.dll +0 -0
  10. package/wwwroot/_content/MindExecution.Shared/css/app.css +1 -1
  11. package/wwwroot/_content/MindExecution.Shared/js/canvas-chat-task-switch.js +122 -0
  12. package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +161 -16
  13. package/wwwroot/_content/MindExecution.Shared/js/mind-map-interactions.js +68 -2
  14. package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-plan-worker.js +44 -8
  15. package/wwwroot/_content/MindExecution.Shared/js/mind-map-logic-workers.js +823 -111
  16. package/wwwroot/_content/MindExecution.Shared/js/mind-map-native-core-bridge.js +399 -0
  17. package/wwwroot/_content/MindExecution.Shared/js/mind-map-node-search-worker.js +53 -10
  18. package/wwwroot/_content/MindExecution.Shared/js/mind-map-visibility-worker.js +71 -11
  19. package/wwwroot/_content/MindExecution.Shared/native-core/mindexec-core-simd.js +18 -0
  20. package/wwwroot/_content/MindExecution.Shared/native-core/mindexec-core-simd.wasm +0 -0
  21. package/wwwroot/_content/MindExecution.Shared/native-core/mindexec-core.js +18 -0
  22. package/wwwroot/_content/MindExecution.Shared/native-core/mindexec-core.wasm +0 -0
  23. package/wwwroot/_content/MindExecution.Shared/native-core/mindexec-native-core-adapter.js +566 -0
  24. package/wwwroot/_content/MindExecution.Shared/native-core/mindexec-native-core-worker.js +89 -0
  25. package/wwwroot/_content/MindExecution.Shared/native-core/native-core-manifest.json +59 -0
  26. package/wwwroot/_framework/BenchmarkUtils.ytz7pn9azy.dll +0 -0
  27. package/wwwroot/_framework/Edgar.9yqq548b45.dll +0 -0
  28. package/wwwroot/_framework/GraphPlanarityTesting.lgk3vd0bh9.dll +0 -0
  29. package/wwwroot/_framework/Microsoft.Win32.SystemEvents.f8qhom7uq5.dll +0 -0
  30. package/wwwroot/_framework/{MindExecution.Core.zs4n1f9hjc.dll → MindExecution.Core.tre9kny2uo.dll} +0 -0
  31. package/wwwroot/_framework/{MindExecution.Kernel.l9idvg7mp7.dll → MindExecution.Kernel.lvfnhwdksf.dll} +0 -0
  32. package/wwwroot/_framework/{MindExecution.Plugins.Admin.l37qx5d8zi.dll → MindExecution.Plugins.Admin.k2vbe4lt64.dll} +0 -0
  33. package/wwwroot/_framework/{MindExecution.Plugins.Business.d7b282yazn.dll → MindExecution.Plugins.Business.u7i3r45xp5.dll} +0 -0
  34. package/wwwroot/_framework/{MindExecution.Plugins.Concept.iw98n0qz5k.dll → MindExecution.Plugins.Concept.7eoasvev17.dll} +0 -0
  35. package/wwwroot/_framework/{MindExecution.Plugins.Directory.cqgn6pa8di.dll → MindExecution.Plugins.Directory.rhh13u0z6c.dll} +0 -0
  36. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.j8xoa8nl57.dll → MindExecution.Plugins.PlanMaster.xzl2x2cqk4.dll} +0 -0
  37. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.zvpzk7om4z.dll → MindExecution.Plugins.YouTube.945tf0zc9m.dll} +0 -0
  38. package/wwwroot/_framework/{MindExecution.Shared.ovot7q606x.dll → MindExecution.Shared.e0w408b0ti.dll} +0 -0
  39. package/wwwroot/_framework/{MindExecution.Web.bzuaz35jv0.dll → MindExecution.Web.vc6ux7xjcw.dll} +0 -0
  40. package/wwwroot/_framework/RangeTree.iw8h1x5u2y.dll +0 -0
  41. package/wwwroot/_framework/System.Collections.Concurrent.jzwteolu29.dll +0 -0
  42. package/wwwroot/_framework/System.Collections.NonGeneric.uqtvwssxuq.dll +0 -0
  43. package/wwwroot/_framework/System.Collections.Specialized.85qtrfslgb.dll +0 -0
  44. package/wwwroot/_framework/System.Collections.xmhu72xgrp.dll +0 -0
  45. package/wwwroot/_framework/System.ComponentModel.Primitives.excbd02v5j.dll +0 -0
  46. package/wwwroot/_framework/System.ComponentModel.TypeConverter.ek2jcwibb9.dll +0 -0
  47. package/wwwroot/_framework/System.ComponentModel.wfe9zz2icv.dll +0 -0
  48. package/wwwroot/_framework/System.Console.5j283so6ps.dll +0 -0
  49. package/wwwroot/_framework/System.Drawing.Common.xzizafq144.dll +0 -0
  50. package/wwwroot/_framework/System.Private.CoreLib.7ouudb2fbl.dll +0 -0
  51. package/wwwroot/_framework/{System.Text.RegularExpressions.cvkox11l6l.dll → System.Text.RegularExpressions.b0vqxbob08.dll} +0 -0
  52. package/wwwroot/_framework/System.Threading.Tasks.Parallel.9tqazg1b0u.dll +0 -0
  53. package/wwwroot/_framework/System.Threading.Tasks.zl4yo3otxa.dll +0 -0
  54. package/wwwroot/_framework/YamlDotNet.dtey92juka.dll +0 -0
  55. package/wwwroot/_framework/blazor.boot.json +63 -45
  56. package/wwwroot/_framework/{dotnet.native.60hhmnxyoo.wasm → dotnet.native.58bvs6q392.wasm} +0 -0
  57. package/wwwroot/_framework/netstandard.he3m3p1s9q.dll +0 -0
  58. package/wwwroot/index.html +2 -1
  59. package/wwwroot/service-worker-assets.js +128 -56
  60. package/wwwroot/service-worker.js +1 -1
  61. package/wwwroot/_framework/System.Collections.3iuoxbe8my.dll +0 -0
  62. package/wwwroot/_framework/System.Collections.Concurrent.vow3rm1dku.dll +0 -0
  63. package/wwwroot/_framework/System.Collections.NonGeneric.npjkaz40oc.dll +0 -0
  64. package/wwwroot/_framework/System.Collections.Specialized.ugkjbs6p02.dll +0 -0
  65. package/wwwroot/_framework/System.ComponentModel.Primitives.end4v0xe2c.dll +0 -0
  66. package/wwwroot/_framework/System.ComponentModel.TypeConverter.tgtp5sm4iv.dll +0 -0
  67. package/wwwroot/_framework/System.ComponentModel.wupoltkk1t.dll +0 -0
  68. package/wwwroot/_framework/System.Console.9dik0wogo2.dll +0 -0
  69. package/wwwroot/_framework/System.Private.CoreLib.7s8wte759y.dll +0 -0
  70. package/wwwroot/_framework/netstandard.b50t77veor.dll +0 -0
@@ -1,12 +1,45 @@
1
1
  (function () {
2
2
  'use strict';
3
3
 
4
- const LOGIC_WORKER_BRIDGE_BUILD_ID = '20260728-cached-viewport-v008';
5
- const VISIBILITY_WORKER_URL = `_content/MindExecution.Shared/js/mind-map-visibility-worker.js?v=${LOGIC_WORKER_BRIDGE_BUILD_ID}`;
4
+ const LOGIC_WORKER_BRIDGE_BUILD_ID = '20260830-native-primary-v013';
5
+ const LEGACY_VISIBILITY_WORKER_URL = `_content/MindExecution.Shared/js/mind-map-visibility-worker.js?v=${LOGIC_WORKER_BRIDGE_BUILD_ID}`;
6
+ const NATIVE_CORE_MODE = String(window.MindMapNativeCoreBridge?.getMode?.() || 'off');
7
+ const NATIVE_PRIMARY_ENABLED = NATIVE_CORE_MODE === 'native';
8
+ const VISIBILITY_WORKER_URL = window.MindMapNativeCoreBridge?.getPrimaryWorkerUrl?.(LEGACY_VISIBILITY_WORKER_URL)
9
+ || LEGACY_VISIBILITY_WORKER_URL;
6
10
  const LOD_PLAN_WORKER_URL = `_content/MindExecution.Shared/js/mind-map-lod-plan-worker.js?v=${LOGIC_WORKER_BRIDGE_BUILD_ID}`;
7
11
  const NODE_SEARCH_WORKER_URL = `_content/MindExecution.Shared/js/mind-map-node-search-worker.js?v=${LOGIC_WORKER_BRIDGE_BUILD_ID}`;
8
12
  const CTRL_WHEEL_SCROLL_ACTIVE_WINDOW_MS = 140;
9
13
  const SEARCH_METADATA_TEXT_LIMIT = 8192;
14
+ const WORKER_TIMEOUT_OVERRIDES = window.__MIND_EXECUTION_WORKER_TIMEOUTS || {};
15
+ const NATIVE_READY_TIMEOUT_MS = normalizeTimeout(WORKER_TIMEOUT_OVERRIDES.nativeReadyMs, 10000, 10, 60000);
16
+ const NATIVE_QUERY_TIMEOUT_MS = normalizeTimeout(WORKER_TIMEOUT_OVERRIDES.nativeQueryMs, 1200, 10, 10000);
17
+ const LEGACY_FALLBACK_READY_TIMEOUT_MS = normalizeTimeout(
18
+ WORKER_TIMEOUT_OVERRIDES.legacyFallbackReadyMs,
19
+ NATIVE_READY_TIMEOUT_MS,
20
+ 10,
21
+ 60000
22
+ );
23
+ const LEGACY_FALLBACK_QUERY_TIMEOUT_MS = normalizeTimeout(
24
+ WORKER_TIMEOUT_OVERRIDES.legacyFallbackQueryMs,
25
+ NATIVE_QUERY_TIMEOUT_MS,
26
+ 10,
27
+ 10000
28
+ );
29
+ const SEARCH_QUERY_TIMEOUT_MS = normalizeTimeout(WORKER_TIMEOUT_OVERRIDES.searchQueryMs, 5000, 10, 30000);
30
+ let nextEngineGeneration = 1;
31
+
32
+ function normalizeTimeout(value, fallback, minimum, maximum) {
33
+ const number = Number(value);
34
+ if (!Number.isFinite(number)) return fallback;
35
+ return Math.max(minimum, Math.min(maximum, Math.floor(number)));
36
+ }
37
+
38
+ function monotonicNow() {
39
+ return (typeof performance !== 'undefined' && typeof performance.now === 'function')
40
+ ? performance.now()
41
+ : Date.now();
42
+ }
10
43
 
11
44
  function getNodeContentType(model) {
12
45
  return String(model?.contentType ?? model?.ContentType ?? '').trim().toLowerCase();
@@ -170,6 +203,10 @@
170
203
  constructor(module) {
171
204
  this.module = module || null;
172
205
  this.boardId = normalizeBoardId(module?.activeBoardId || window.mindMap?.activeBoardId || '');
206
+ this._engineGeneration = nextEngineGeneration++;
207
+ this._boardEpoch = 0;
208
+ this._boardRevision = 0;
209
+ this._searchRevision = 0;
173
210
 
174
211
  this._visibilityWorker = null;
175
212
  this._lodPlanWorker = null;
@@ -178,6 +215,16 @@
178
215
  this._lodPlanReady = false;
179
216
  this._searchReady = false;
180
217
  this._visibilityDisabled = false;
218
+ this._visibilityBackend = NATIVE_PRIMARY_ENABLED ? 'native' : 'legacy';
219
+ this._nativeFallbackActive = false;
220
+ this._disposed = false;
221
+ this._visibilityReadyTimer = null;
222
+ this._visibilityQueryTimer = null;
223
+ this._hitTestQueryTimer = null;
224
+ this._visibilityQueryStartedAt = 0;
225
+ this._hitTestQueryStartedAt = 0;
226
+ this._visibilityNodeIds = new Set();
227
+ this._visibilityNodeSnapshots = new Map();
181
228
  this._lodPlanDisabled = false;
182
229
  this._searchDisabled = false;
183
230
  this._latestVisibilityRequestSeq = 0;
@@ -188,21 +235,31 @@
188
235
  this._pendingLodPlanKey = '';
189
236
  this._visibilityQueryPending = false;
190
237
  this._inFlightVisibilityRequestSeq = 0;
238
+ this._visibilityQueryStartedAt = 0;
191
239
  this._deferredVisibilityPayload = null;
192
240
  this._hitTestQueryPending = false;
241
+ this._inFlightHitTestRequestSeq = 0;
242
+ this._hitTestQueryStartedAt = 0;
243
+ this._inFlightHitTestPayload = null;
193
244
  this._deferredHitTestPayload = null;
194
245
  this._pendingSelectionResolvers = new Map();
195
246
  this._pendingSearchResolvers = new Map();
247
+ this._nativeCoreShadow = window.MindMapNativeCoreBridge?.createShadow?.() || null;
196
248
 
197
249
  this._createWorkers();
198
250
  }
199
251
 
200
252
  _createWorkers() {
201
- this._visibilityWorker = this._createWorker(
202
- 'visibility',
203
- VISIBILITY_WORKER_URL,
204
- (data) => this._handleVisibilityMessage(data)
205
- );
253
+ if (!this._startVisibilityWorker(
254
+ NATIVE_PRIMARY_ENABLED ? 'native' : 'legacy',
255
+ VISIBILITY_WORKER_URL
256
+ )) {
257
+ if (NATIVE_PRIMARY_ENABLED) {
258
+ this._activateLegacyVisibilityFallback('native visibility worker creation failed');
259
+ } else {
260
+ this._visibilityDisabled = true;
261
+ }
262
+ }
206
263
  this._lodPlanWorker = this._createWorker(
207
264
  'lod-plan',
208
265
  LOD_PLAN_WORKER_URL,
@@ -213,12 +270,54 @@
213
270
  NODE_SEARCH_WORKER_URL,
214
271
  (data) => this._handleSearchMessage(data)
215
272
  );
273
+ this._lodPlanDisabled = !this._lodPlanWorker;
274
+ this._searchDisabled = !this._searchWorker;
216
275
  }
217
276
 
218
- _createWorker(kind, url, onMessage) {
277
+ _startVisibilityWorker(backend, url) {
278
+ this._visibilityBackend = backend;
279
+ this._visibilityReady = false;
280
+ this._visibilityDisabled = false;
281
+ const worker = this._createWorker(
282
+ 'visibility',
283
+ url,
284
+ (data) => this._handleVisibilityMessage(data),
285
+ { backend }
286
+ );
287
+ this._visibilityWorker = worker;
288
+ if (!worker) {
289
+ return false;
290
+ }
291
+
292
+ const fallbackLegacy = backend === 'legacy' && this._nativeFallbackActive === true;
293
+ if (backend === 'native' || fallbackLegacy) {
294
+ const timeoutMs = backend === 'native'
295
+ ? NATIVE_READY_TIMEOUT_MS
296
+ : LEGACY_FALLBACK_READY_TIMEOUT_MS;
297
+ this._clearVisibilityReadyTimer();
298
+ this._visibilityReadyTimer = setTimeout(() => {
299
+ if (this._disposed !== true &&
300
+ this._visibilityWorker === worker &&
301
+ this._visibilityBackend === backend &&
302
+ this._visibilityReady !== true) {
303
+ if (backend === 'native') {
304
+ this._activateLegacyVisibilityFallback(`native READY timeout after ${timeoutMs}ms`);
305
+ } else if (this._nativeFallbackActive === true) {
306
+ this._disableVisibilityWorker(`legacy fallback READY timeout after ${timeoutMs}ms`);
307
+ }
308
+ }
309
+ }, timeoutMs);
310
+ }
311
+ return true;
312
+ }
313
+
314
+ _createWorker(kind, url, onMessage, options = {}) {
219
315
  try {
220
316
  const worker = new Worker(url);
221
317
  worker.onmessage = (event) => {
318
+ if (this._disposed === true || (kind === 'visibility' && worker !== this._visibilityWorker)) {
319
+ return;
320
+ }
222
321
  const data = event?.data || null;
223
322
  if (!data || typeof data !== 'object') {
224
323
  return;
@@ -226,7 +325,14 @@
226
325
 
227
326
  if (data.type === 'READY') {
228
327
  if (kind === 'visibility') {
328
+ this._clearVisibilityReadyTimer();
229
329
  this._visibilityReady = true;
330
+ this._visibilityDisabled = false;
331
+ if (options.backend === 'native') {
332
+ window.MindMapNativeCoreBridge?.notifyPrimaryReady?.(data);
333
+ } else if (this._nativeFallbackActive === true) {
334
+ window.MindMapNativeCoreBridge?.notifyFallbackReady?.();
335
+ }
230
336
  } else if (kind === 'lod-plan') {
231
337
  this._lodPlanReady = true;
232
338
  } else if (kind === 'node-search') {
@@ -238,17 +344,16 @@
238
344
  onMessage(data);
239
345
  };
240
346
  worker.onerror = (error) => {
347
+ if (this._disposed === true || (kind === 'visibility' && worker !== this._visibilityWorker)) {
348
+ return;
349
+ }
241
350
  console.warn(`[MindMapLogicWorkerBridge] ${kind} worker disabled after error.`, error);
242
351
  if (kind === 'visibility') {
243
- this._visibilityDisabled = true;
244
- this._visibilityReady = false;
245
- this._visibilityQueryPending = false;
246
- this._inFlightVisibilityRequestSeq = 0;
247
- this._deferredVisibilityPayload = null;
248
- this._clearHitTestCache(this.module);
249
- this._hitTestQueryPending = false;
250
- this._deferredHitTestPayload = null;
251
- this._resolvePendingSelectionQueries(null);
352
+ if (options.backend === 'native') {
353
+ this._activateLegacyVisibilityFallback(String(error?.message || 'native visibility worker error'));
354
+ return;
355
+ }
356
+ this._disableVisibilityWorker(String(error?.message || 'legacy visibility worker error'));
252
357
  } else if (kind === 'lod-plan') {
253
358
  this._lodPlanDisabled = true;
254
359
  this._lodPlanReady = false;
@@ -257,30 +362,31 @@
257
362
  this._searchReady = false;
258
363
  this._resolvePendingSearches(null);
259
364
  }
260
- try {
261
- worker.terminate();
262
- } catch { }
365
+ if (kind !== 'visibility') {
366
+ try {
367
+ worker.terminate();
368
+ } catch { }
369
+ }
263
370
  };
264
371
  return worker;
265
372
  } catch (error) {
266
373
  console.warn(`[MindMapLogicWorkerBridge] Failed to create ${kind} worker.`, error);
267
- if (kind === 'visibility') {
268
- this._visibilityDisabled = true;
269
- } else if (kind === 'lod-plan') {
374
+ if (kind === 'lod-plan') {
270
375
  this._lodPlanDisabled = true;
271
- } else {
376
+ } else if (kind === 'node-search') {
272
377
  this._searchDisabled = true;
273
378
  }
274
379
  return null;
275
380
  }
276
381
  }
277
382
 
278
- _post(worker, payload) {
383
+ _post(worker, payload, boardRevision = this._boardRevision) {
279
384
  if (!worker || !payload) {
280
385
  return false;
281
386
  }
282
387
 
283
388
  try {
389
+ this._stampOwnerPayload(payload, boardRevision);
284
390
  worker.postMessage(payload);
285
391
  return true;
286
392
  } catch (error) {
@@ -289,28 +395,285 @@
289
395
  }
290
396
  }
291
397
 
398
+ _postVisibility(payload) {
399
+ this._stampOwnerPayload(payload);
400
+ const posted = this._post(this._visibilityWorker, payload);
401
+ this._nativeCoreShadow?.post?.(payload);
402
+ if (!posted) {
403
+ if (this._visibilityBackend === 'native') {
404
+ this._activateLegacyVisibilityFallback(`native ${String(payload?.type || 'message')} post failed`);
405
+ } else if (this._visibilityBackend === 'legacy' && this._nativeFallbackActive === true) {
406
+ this._disableVisibilityWorker(`legacy fallback ${String(payload?.type || 'message')} post failed`);
407
+ }
408
+ }
409
+ return posted;
410
+ }
411
+
412
+ _postSearch(payload) {
413
+ return this._post(this._searchWorker, payload, this._searchRevision);
414
+ }
415
+
416
+ _clearVisibilityReadyTimer() {
417
+ if (this._visibilityReadyTimer !== null) {
418
+ clearTimeout(this._visibilityReadyTimer);
419
+ this._visibilityReadyTimer = null;
420
+ }
421
+ }
422
+
423
+ _clearVisibilityQueryTimer() {
424
+ if (this._visibilityQueryTimer !== null) {
425
+ clearTimeout(this._visibilityQueryTimer);
426
+ this._visibilityQueryTimer = null;
427
+ }
428
+ }
429
+
430
+ _clearHitTestQueryTimer() {
431
+ if (this._hitTestQueryTimer !== null) {
432
+ clearTimeout(this._hitTestQueryTimer);
433
+ this._hitTestQueryTimer = null;
434
+ }
435
+ }
436
+
437
+ _armVisibilityQueryTimer(seq) {
438
+ this._clearVisibilityQueryTimer();
439
+ const backend = this._visibilityBackend;
440
+ const fallbackLegacy = backend === 'legacy' && this._nativeFallbackActive === true;
441
+ if (backend !== 'native' && !fallbackLegacy) return;
442
+ const timeoutMs = backend === 'native'
443
+ ? NATIVE_QUERY_TIMEOUT_MS
444
+ : LEGACY_FALLBACK_QUERY_TIMEOUT_MS;
445
+ const expectedSeq = Math.max(0, Math.floor(Number(seq || 0)));
446
+ this._visibilityQueryTimer = setTimeout(() => {
447
+ if (this._disposed !== true &&
448
+ this._visibilityBackend === backend &&
449
+ this._visibilityQueryPending === true &&
450
+ Number(this._inFlightVisibilityRequestSeq || 0) === expectedSeq) {
451
+ if (backend === 'native') {
452
+ this._activateLegacyVisibilityFallback(
453
+ `native VISIBILITY_QUERY ${expectedSeq} timeout after ${timeoutMs}ms`
454
+ );
455
+ } else if (this._nativeFallbackActive === true) {
456
+ this._disableVisibilityWorker(
457
+ `legacy fallback VISIBILITY_QUERY ${expectedSeq} timeout after ${timeoutMs}ms`
458
+ );
459
+ }
460
+ }
461
+ }, timeoutMs);
462
+ }
463
+
464
+ _armHitTestQueryTimer(seq) {
465
+ this._clearHitTestQueryTimer();
466
+ const backend = this._visibilityBackend;
467
+ const fallbackLegacy = backend === 'legacy' && this._nativeFallbackActive === true;
468
+ if (backend !== 'native' && !fallbackLegacy) return;
469
+ const timeoutMs = backend === 'native'
470
+ ? NATIVE_QUERY_TIMEOUT_MS
471
+ : LEGACY_FALLBACK_QUERY_TIMEOUT_MS;
472
+ const expectedSeq = Math.max(0, Math.floor(Number(seq || 0)));
473
+ this._hitTestQueryTimer = setTimeout(() => {
474
+ if (this._disposed !== true &&
475
+ this._visibilityBackend === backend &&
476
+ this._hitTestQueryPending === true &&
477
+ Number(this._inFlightHitTestRequestSeq || 0) === expectedSeq) {
478
+ if (backend === 'native') {
479
+ this._activateLegacyVisibilityFallback(
480
+ `native HIT_TEST_QUERY ${expectedSeq} timeout after ${timeoutMs}ms`
481
+ );
482
+ } else if (this._nativeFallbackActive === true) {
483
+ this._disableVisibilityWorker(
484
+ `legacy fallback HIT_TEST_QUERY ${expectedSeq} timeout after ${timeoutMs}ms`
485
+ );
486
+ }
487
+ }
488
+ }, timeoutMs);
489
+ }
490
+
491
+ _armSelectionQueryTimer(seq, pending) {
492
+ const backend = this._visibilityBackend;
493
+ const fallbackLegacy = backend === 'legacy' && this._nativeFallbackActive === true;
494
+ if (!pending || (backend !== 'native' && !fallbackLegacy)) return;
495
+ const timeoutMs = backend === 'native'
496
+ ? NATIVE_QUERY_TIMEOUT_MS
497
+ : LEGACY_FALLBACK_QUERY_TIMEOUT_MS;
498
+ pending.timer = setTimeout(() => {
499
+ if (!this._pendingSelectionResolvers.has(seq) ||
500
+ this._disposed === true ||
501
+ this._visibilityBackend !== backend) {
502
+ return;
503
+ }
504
+ if (backend === 'native') {
505
+ this._activateLegacyVisibilityFallback(
506
+ `native SELECTION_QUERY ${seq} timeout after ${timeoutMs}ms`
507
+ );
508
+ } else if (this._nativeFallbackActive === true) {
509
+ this._disableVisibilityWorker(
510
+ `legacy fallback SELECTION_QUERY ${seq} timeout after ${timeoutMs}ms`
511
+ );
512
+ }
513
+ }, timeoutMs);
514
+ }
515
+
516
+ _wakeVisibilityFallback(reason) {
517
+ const module = this.module;
518
+ if (!module) return;
519
+ module._forceUpdateFrames = Math.max(Number(module._forceUpdateFrames || 0), 1);
520
+ module.requestAnimationWake?.(String(reason || 'visibility-fallback'));
521
+ }
522
+
523
+ _resetPendingVisibilityQueries() {
524
+ this._clearVisibilityQueryTimer();
525
+ this._clearHitTestQueryTimer();
526
+ this._visibilityQueryPending = false;
527
+ this._inFlightVisibilityRequestSeq = 0;
528
+ this._deferredVisibilityPayload = null;
529
+ this._hitTestQueryPending = false;
530
+ this._inFlightHitTestRequestSeq = 0;
531
+ this._inFlightHitTestPayload = null;
532
+ this._deferredHitTestPayload = null;
533
+ this._clearHitTestCache(this.module);
534
+ this._resolvePendingSelectionQueries(null);
535
+ }
536
+
537
+ _disableVisibilityWorker(reason) {
538
+ const worker = this._visibilityWorker;
539
+ this._visibilityWorker = null;
540
+ this._visibilityDisabled = true;
541
+ this._visibilityReady = false;
542
+ this._clearVisibilityReadyTimer();
543
+ this._resetPendingVisibilityQueries();
544
+ if (this.module) {
545
+ this.module._visibilityWorkerPrimed = false;
546
+ this.module._preserveVisibleSetThisFrame = false;
547
+ }
548
+ if (this._nativeFallbackActive === true) {
549
+ window.MindMapNativeCoreBridge?.notifyFallbackUnavailable?.(reason);
550
+ this._wakeVisibilityFallback('native-primary-main-thread-fallback');
551
+ }
552
+ try { worker?.terminate?.(); } catch { }
553
+ window.MindCanvasTrace?.emit?.('logicWorker.visibilityDisabled', {
554
+ backend: this._visibilityBackend,
555
+ reason: String(reason || 'visibility worker disabled')
556
+ });
557
+ }
558
+
559
+ _snapshotCurrentSpatialNodes(module = this.module) {
560
+ const nodes = Array.from(this._visibilityNodeSnapshots.values());
561
+ this._visibilityNodeIds = new Set(nodes.map((node) => node.id));
562
+ return nodes;
563
+ }
564
+
565
+ _synchronizeVisibilityWorker(worker, reason) {
566
+ const module = this.module;
567
+ const boardId = this._resolveBoardId(module);
568
+ if (!worker || !module || !boardId) return false;
569
+ return this._post(worker, {
570
+ type: 'REBUILD_BOARD',
571
+ boardId,
572
+ reason: String(reason || 'visibility-worker-synchronize'),
573
+ cellSize: Math.max(1, Number(module.GRID_CELL_SIZE || 500)),
574
+ nodes: this._snapshotCurrentSpatialNodes(module)
575
+ });
576
+ }
577
+
578
+ _activateLegacyVisibilityFallback(reason) {
579
+ if (this._disposed === true || this._visibilityBackend !== 'native') {
580
+ return false;
581
+ }
582
+
583
+ const message = String(reason || 'native visibility fallback');
584
+ const nativeWorker = this._visibilityWorker;
585
+ this._visibilityWorker = null;
586
+ this._visibilityReady = false;
587
+ this._visibilityDisabled = false;
588
+ this._nativeFallbackActive = true;
589
+ this._clearVisibilityReadyTimer();
590
+ this._resetPendingVisibilityQueries();
591
+ if (this.module) {
592
+ this.module._visibilityWorkerPrimed = false;
593
+ this.module._preserveVisibleSetThisFrame = false;
594
+ }
595
+ this._wakeVisibilityFallback('native-primary-fallback');
596
+ try { nativeWorker?.terminate?.(); } catch { }
597
+
598
+ window.MindMapNativeCoreBridge?.notifyPrimaryFallback?.(message);
599
+ console.warn('[MindMapLogicWorkerBridge] Native visibility fell back to the JS worker.', message);
600
+
601
+ if (!this._startVisibilityWorker('legacy', LEGACY_VISIBILITY_WORKER_URL)) {
602
+ this._disableVisibilityWorker('legacy visibility fallback creation failed');
603
+ return false;
604
+ }
605
+
606
+ if (!this._synchronizeVisibilityWorker(this._visibilityWorker, 'native-primary-fallback')) {
607
+ this._disableVisibilityWorker('legacy visibility fallback synchronization failed');
608
+ return false;
609
+ }
610
+ return true;
611
+ }
612
+
613
+ _handleVisibilityProtocolFailure(reason) {
614
+ if (this._visibilityBackend === 'native') {
615
+ this._activateLegacyVisibilityFallback(reason);
616
+ } else {
617
+ this._disableVisibilityWorker(reason);
618
+ }
619
+ }
620
+
621
+ _stampOwnerPayload(payload, boardRevision = this._boardRevision) {
622
+ if (!payload || typeof payload !== 'object') {
623
+ return payload;
624
+ }
625
+
626
+ payload.engineGeneration = Number(this._engineGeneration || 0);
627
+ payload.boardEpoch = Number(this._boardEpoch || 0);
628
+ payload.boardRevision = Number(boardRevision || 0);
629
+ return payload;
630
+ }
631
+
632
+ _isCurrentWorkerOwner(data, boardId = this._resolveBoardId(this.module), boardRevision = this._boardRevision) {
633
+ const owner = this._classifyWorkerOwner(data, boardId, boardRevision);
634
+ return owner.identityMatches && owner.revisionMatches;
635
+ }
636
+
637
+ _classifyWorkerOwner(data, boardId = this._resolveBoardId(this.module), boardRevision = this._boardRevision) {
638
+ const identityMatches = normalizeBoardId(data?.boardId) === normalizeBoardId(boardId) &&
639
+ Number(data?.engineGeneration || 0) === Number(this._engineGeneration || 0) &&
640
+ Number(data?.boardEpoch || 0) === Number(this._boardEpoch || 0);
641
+ const revisionMatches = Number(data?.boardRevision || 0) === Number(boardRevision || 0);
642
+ return { identityMatches, revisionMatches };
643
+ }
644
+
292
645
  _stampVisibilityPayloadForPost(payload) {
293
646
  if (!payload || String(payload.type || '') !== 'VISIBILITY_QUERY') {
294
647
  return payload;
295
648
  }
296
649
 
297
- payload.mainLastAppliedSeq = Math.max(
298
- 0,
299
- Math.floor(Number(this.module?._workerVisibilitySeq || 0))
300
- );
650
+ payload.mainLastAppliedSeq = this.module?._visibilityWorkerPrimed === true
651
+ ? Math.max(0, Math.floor(Number(this.module?._workerVisibilitySeq || 0)))
652
+ : 0;
301
653
  return payload;
302
654
  }
303
655
 
304
- _resolveBoardId(module = this.module, fallbackBoardId = null) {
305
- const candidate = normalizeBoardId(
306
- fallbackBoardId ??
307
- module?.activeBoardId ??
308
- window.mindMap?.activeBoardId ??
309
- this.boardId
310
- );
311
- if (candidate) {
312
- this.boardId = candidate;
656
+ _resolveBoardId(module = this.module, fallbackBoardId = undefined) {
657
+ let sourceValue;
658
+ let ownerSourcePresent = false;
659
+ if (fallbackBoardId !== undefined) {
660
+ sourceValue = fallbackBoardId;
661
+ ownerSourcePresent = true;
662
+ } else if (module && 'activeBoardId' in Object(module)) {
663
+ sourceValue = module.activeBoardId;
664
+ ownerSourcePresent = true;
665
+ } else if (typeof module?.getCurrentBoardId === 'function') {
666
+ sourceValue = module.getCurrentBoardId();
667
+ ownerSourcePresent = true;
668
+ } else if (window.mindMap && 'activeBoardId' in Object(window.mindMap)) {
669
+ sourceValue = window.mindMap.activeBoardId;
670
+ ownerSourcePresent = true;
671
+ } else {
672
+ sourceValue = this.boardId;
313
673
  }
674
+
675
+ const candidate = normalizeBoardId(sourceValue);
676
+ if (ownerSourcePresent || candidate) this.boardId = candidate;
314
677
  return candidate;
315
678
  }
316
679
 
@@ -330,15 +693,156 @@
330
693
  module._workerHitTestProfile = null;
331
694
  }
332
695
 
696
+ _validateWorkerNodeIds(source, required = true) {
697
+ if (!Array.isArray(source)) {
698
+ return required ? { valid: false, ids: [] } : { valid: true, ids: null };
699
+ }
700
+
701
+ const ids = [];
702
+ const seen = new Set();
703
+ for (let index = 0; index < source.length; index++) {
704
+ const nodeId = String(source[index] || '').trim();
705
+ if (!nodeId || seen.has(nodeId) || !this._visibilityNodeIds.has(nodeId)) {
706
+ return { valid: false, ids: [] };
707
+ }
708
+ seen.add(nodeId);
709
+ ids.push(nodeId);
710
+ }
711
+ return { valid: true, ids };
712
+ }
713
+
714
+ _validateWorkerRect(source) {
715
+ if (!source || typeof source !== 'object') return false;
716
+ const left = Number(source.left);
717
+ const right = Number(source.right);
718
+ const bottom = Number(source.bottom);
719
+ const top = Number(source.top);
720
+ return Number.isFinite(left) && Number.isFinite(right) &&
721
+ Number.isFinite(bottom) && Number.isFinite(top) &&
722
+ right >= left && top >= bottom;
723
+ }
724
+
725
+ _validateVisibilityResult(data) {
726
+ if (String(data?.type || '') !== 'VISIBILITY_RESULT') {
727
+ return { valid: false, reason: 'unexpected visibility result type' };
728
+ }
729
+ const visibleCount = Number(data.visibleCount);
730
+ if (!Number.isInteger(visibleCount) || visibleCount < 0 || visibleCount > this._visibilityNodeIds.size) {
731
+ return { valid: false, reason: 'invalid visibleCount' };
732
+ }
733
+ if (typeof data.visibilityChanged !== 'boolean' ||
734
+ typeof data.fullResVisibilityReconcilePending !== 'boolean' ||
735
+ !this._validateWorkerRect(data.viewportRect) ||
736
+ Number(data.lastVisibilityNodeCount) !== this._visibilityNodeIds.size) {
737
+ return { valid: false, reason: 'invalid visibility state fields' };
738
+ }
739
+
740
+ const entered = this._validateWorkerNodeIds(data.enteredIds, true);
741
+ const exited = this._validateWorkerNodeIds(data.exitedIds, true);
742
+ const visible = this._validateWorkerNodeIds(data.visibleIds, false);
743
+ const priority = this._validateWorkerNodeIds(data.priorityIds, false);
744
+ if (!entered.valid || !exited.valid || !visible.valid || !priority.valid) {
745
+ return { valid: false, reason: 'invalid or unknown visibility node id' };
746
+ }
747
+ const exitedSet = new Set(exited.ids);
748
+ if (entered.ids.some((nodeId) => exitedSet.has(nodeId))) {
749
+ return { valid: false, reason: 'visibility enter and exit overlap' };
750
+ }
751
+ if (data.visibilityChanged !== (entered.ids.length > 0 || exited.ids.length > 0)) {
752
+ return { valid: false, reason: 'visibilityChanged does not match diff' };
753
+ }
754
+
755
+ const lastAppliedSeq = Math.max(0, Math.floor(Number(this.module?._workerVisibilitySeq || 0)));
756
+ const resultSeq = Math.max(0, Math.floor(Number(data.seq || 0)));
757
+ const hasExactDiffBase = this.module?._visibilityWorkerPrimed === true &&
758
+ lastAppliedSeq > 0 && resultSeq === lastAppliedSeq + 1;
759
+ if (!hasExactDiffBase && visible.ids === null) {
760
+ return { valid: false, reason: 'full visibility result is required without an exact diff base' };
761
+ }
762
+
763
+ let expectedVisibleIds = visible.ids !== null ? new Set(visible.ids) : new Set();
764
+ if (hasExactDiffBase) {
765
+ const previousVisibleIds = new Set();
766
+ this.module?._visibleIds?.forEach?.((nodeId) => {
767
+ const normalizedId = String(nodeId || '').trim();
768
+ if (this._visibilityNodeIds.has(normalizedId)) previousVisibleIds.add(normalizedId);
769
+ });
770
+ for (let index = 0; index < entered.ids.length; index++) {
771
+ if (previousVisibleIds.has(entered.ids[index])) {
772
+ return { valid: false, reason: 'entered visibility id was already visible' };
773
+ }
774
+ }
775
+ for (let index = 0; index < exited.ids.length; index++) {
776
+ if (!previousVisibleIds.has(exited.ids[index])) {
777
+ return { valid: false, reason: 'exited visibility id was not visible' };
778
+ }
779
+ }
780
+ for (let index = 0; index < exited.ids.length; index++) previousVisibleIds.delete(exited.ids[index]);
781
+ for (let index = 0; index < entered.ids.length; index++) previousVisibleIds.add(entered.ids[index]);
782
+ if (visible.ids !== null &&
783
+ (previousVisibleIds.size !== expectedVisibleIds.size ||
784
+ Array.from(previousVisibleIds).some((nodeId) => !expectedVisibleIds.has(nodeId)))) {
785
+ return { valid: false, reason: 'full visibility set does not match diff' };
786
+ }
787
+ expectedVisibleIds = previousVisibleIds;
788
+ }
789
+
790
+ if (expectedVisibleIds.size !== visibleCount) {
791
+ return { valid: false, reason: 'visibleCount does not match visible set' };
792
+ }
793
+ if (priority.ids !== null) {
794
+ if (priority.ids.length !== visibleCount ||
795
+ priority.ids.some((nodeId) => !expectedVisibleIds.has(nodeId))) {
796
+ return { valid: false, reason: 'priorityIds do not match visible set' };
797
+ }
798
+ }
799
+ return { valid: true, reason: '' };
800
+ }
801
+
802
+ _validateHitTestResult(data, payload) {
803
+ const candidates = this._validateWorkerNodeIds(data?.candidateIds, true);
804
+ if (String(data?.type || '') !== 'HIT_TEST_RESULT' || !candidates.valid || !payload) return false;
805
+ return String(data.rangeKey || '') === `${payload.cellX}:${payload.cellY}:${payload.cellRadius}` &&
806
+ Number(data.cellX) === Number(payload.cellX) &&
807
+ Number(data.cellY) === Number(payload.cellY) &&
808
+ Number(data.cellRadius) === Number(payload.cellRadius);
809
+ }
810
+
811
+ _validateSelectionResult(data, payload) {
812
+ const selected = this._validateWorkerNodeIds(data?.selectedIds, true);
813
+ if (String(data?.type || '') !== 'SELECTION_QUERY_RESULT' || !selected.valid || !payload) return false;
814
+ const expected = payload.selectionRect;
815
+ const actual = data.selectionRect;
816
+ if (!this._validateWorkerRect(actual) || !expected || typeof expected !== 'object') return false;
817
+ const expectedLeft = Number(expected.left);
818
+ const expectedRight = Number(expected.right);
819
+ const expectedBottom = Number(expected.bottom);
820
+ const expectedTop = Number(expected.top);
821
+ if (!Number.isFinite(expectedLeft) || !Number.isFinite(expectedRight) ||
822
+ !Number.isFinite(expectedBottom) || !Number.isFinite(expectedTop)) return false;
823
+ const epsilon = 0.000001;
824
+ return Math.abs(Number(actual.left) - Math.min(expectedLeft, expectedRight)) <= epsilon &&
825
+ Math.abs(Number(actual.right) - Math.max(expectedLeft, expectedRight)) <= epsilon &&
826
+ Math.abs(Number(actual.bottom) - Math.min(expectedBottom, expectedTop)) <= epsilon &&
827
+ Math.abs(Number(actual.top) - Math.max(expectedBottom, expectedTop)) <= epsilon;
828
+ }
829
+
333
830
  _handleVisibilityMessage(data) {
334
- if (String(data?.type || '') === 'HIT_TEST_RESULT') {
831
+ const messageType = String(data?.type || '');
832
+ if (messageType === 'NATIVE_CORE_INIT_ERROR' || messageType === 'NATIVE_CORE_RUNTIME_ERROR') {
833
+ this._handleVisibilityProtocolFailure(String(data?.message || messageType));
834
+ return;
835
+ }
836
+ this._nativeCoreShadow?.observePrimaryResult?.(data);
837
+ if (messageType === 'HIT_TEST_RESULT') {
335
838
  this._handleHitTestMessage(data);
336
839
  return;
337
840
  }
338
- if (String(data?.type || '') === 'SELECTION_QUERY_RESULT') {
841
+ if (messageType === 'SELECTION_QUERY_RESULT') {
339
842
  this._handleSelectionQueryMessage(data);
340
843
  return;
341
844
  }
845
+ if (messageType !== 'VISIBILITY_RESULT') return;
342
846
 
343
847
  const module = this.module;
344
848
  const boardId = this._resolveBoardId(module);
@@ -346,42 +850,142 @@
346
850
  return;
347
851
  }
348
852
 
349
- if (normalizeBoardId(data.boardId) !== boardId) {
350
- return;
351
- }
352
-
353
853
  const resultSeq = Math.max(0, Math.floor(Number(data.seq || 0)));
354
854
  const inFlightSeq = Math.max(0, Math.floor(Number(this._inFlightVisibilityRequestSeq || 0)));
355
855
  if (resultSeq <= 0 || resultSeq !== inFlightSeq) {
356
856
  return;
357
857
  }
858
+ const roundTripMs = this._visibilityQueryStartedAt > 0
859
+ ? Math.max(0, monotonicNow() - this._visibilityQueryStartedAt)
860
+ : 0;
861
+
862
+ const owner = this._classifyWorkerOwner(data, boardId);
863
+ if (data?.stale === true || !owner.identityMatches || !owner.revisionMatches) {
864
+ this._clearVisibilityQueryTimer();
865
+ this._visibilityQueryPending = false;
866
+ this._inFlightVisibilityRequestSeq = 0;
867
+ this._visibilityQueryStartedAt = 0;
868
+ module._visibilityWorkerPrimed = false;
869
+ module._preserveVisibleSetThisFrame = false;
870
+ if (!owner.identityMatches) {
871
+ if (this._visibilityBackend === 'native' || this._nativeFallbackActive === true) {
872
+ this._handleVisibilityProtocolFailure('visibility result owner identity mismatch');
873
+ } else {
874
+ this._flushDeferredVisibilityRequest();
875
+ }
876
+ return;
877
+ }
878
+ if ((this._visibilityBackend === 'native' || this._nativeFallbackActive === true) &&
879
+ data?.stale === true && owner.revisionMatches) {
880
+ this._handleVisibilityProtocolFailure(
881
+ this._visibilityBackend === 'native'
882
+ ? 'native rejected a current visibility owner'
883
+ : 'legacy fallback rejected a current visibility owner'
884
+ );
885
+ return;
886
+ }
887
+ this._flushDeferredVisibilityRequest();
888
+ return;
889
+ }
358
890
 
359
- module._applyWorkerVisibilityResult?.(data);
891
+ const validation = this._validateVisibilityResult(data);
892
+ if (!validation.valid) {
893
+ this._clearVisibilityQueryTimer();
894
+ this._visibilityQueryPending = false;
895
+ this._inFlightVisibilityRequestSeq = 0;
896
+ this._visibilityQueryStartedAt = 0;
897
+ module._visibilityWorkerPrimed = false;
898
+ module._preserveVisibleSetThisFrame = false;
899
+ this._handleVisibilityProtocolFailure(validation.reason);
900
+ return;
901
+ }
902
+
903
+ this._clearVisibilityQueryTimer();
904
+ try {
905
+ if (typeof module._applyWorkerVisibilityResult !== 'function') {
906
+ throw new Error('renderer visibility apply hook is missing');
907
+ }
908
+ module._applyWorkerVisibilityResult(data);
909
+ if (Math.max(0, Math.floor(Number(module._workerVisibilitySeq || 0))) !== resultSeq) {
910
+ throw new Error(`renderer did not acknowledge visibility seq ${resultSeq}`);
911
+ }
912
+ } catch (error) {
913
+ const message = `visibility result apply failed: ${String(error?.message || error || 'unknown error')}`;
914
+ window.MindCanvasTrace?.emit?.('logicWorker.visibilityApplyFailed', {
915
+ backend: this._visibilityBackend,
916
+ resultType: String(data?.type || ''),
917
+ seq: resultSeq,
918
+ reason: message
919
+ });
920
+ console.error('[MindMapLogicWorkerBridge] Visibility result apply failed.', error);
921
+ this._handleVisibilityProtocolFailure(message);
922
+ return;
923
+ }
924
+ if (this._visibilityBackend === 'native') {
925
+ window.MindMapNativeCoreBridge?.notifyPrimaryResultAccepted?.(data, roundTripMs);
926
+ }
360
927
  this._visibilityQueryPending = false;
361
928
  this._inFlightVisibilityRequestSeq = 0;
929
+ this._visibilityQueryStartedAt = 0;
362
930
  this._flushDeferredVisibilityRequest();
363
931
  }
364
932
 
365
933
  _handleHitTestMessage(data) {
366
934
  const module = this.module;
367
935
  const boardId = this._resolveBoardId(module);
936
+ const seq = Number(data?.seq || 0);
937
+ if (seq !== Number(this._inFlightHitTestRequestSeq || 0)) {
938
+ return;
939
+ }
940
+
941
+ const inFlightPayload = this._inFlightHitTestPayload;
942
+ const roundTripMs = this._hitTestQueryStartedAt > 0
943
+ ? Math.max(0, monotonicNow() - this._hitTestQueryStartedAt)
944
+ : 0;
945
+ this._clearHitTestQueryTimer();
368
946
  this._hitTestQueryPending = false;
947
+ this._inFlightHitTestRequestSeq = 0;
948
+ this._hitTestQueryStartedAt = 0;
949
+ this._inFlightHitTestPayload = null;
369
950
 
370
951
  if (!module || !boardId) {
371
952
  this._flushDeferredHitTestRequest();
372
953
  return;
373
954
  }
374
955
 
375
- if (normalizeBoardId(data.boardId) !== boardId) {
956
+ const owner = this._classifyWorkerOwner(data, boardId);
957
+ if (data?.stale === true || !owner.identityMatches || !owner.revisionMatches) {
958
+ if (!owner.identityMatches) {
959
+ if (this._visibilityBackend === 'native' || this._nativeFallbackActive === true) {
960
+ this._handleVisibilityProtocolFailure('hit-test result owner identity mismatch');
961
+ } else {
962
+ this._flushDeferredHitTestRequest();
963
+ }
964
+ return;
965
+ }
966
+ if ((this._visibilityBackend === 'native' || this._nativeFallbackActive === true) &&
967
+ data?.stale === true && owner.revisionMatches) {
968
+ this._handleVisibilityProtocolFailure(
969
+ this._visibilityBackend === 'native'
970
+ ? 'native rejected a current hit-test owner'
971
+ : 'legacy fallback rejected a current hit-test owner'
972
+ );
973
+ return;
974
+ }
376
975
  this._flushDeferredHitTestRequest();
377
976
  return;
378
977
  }
379
978
 
380
- if (Number(data.seq || 0) < Number(this._latestHitTestRequestSeq || 0)) {
979
+ if (seq !== Number(this._latestHitTestRequestSeq || 0)) {
381
980
  this._flushDeferredHitTestRequest();
382
981
  return;
383
982
  }
384
983
 
984
+ if (!this._validateHitTestResult(data, inFlightPayload)) {
985
+ this._handleVisibilityProtocolFailure('invalid hit-test result payload');
986
+ return;
987
+ }
988
+
385
989
  module._workerHitTestRangeKey = String(data.rangeKey || '').trim();
386
990
  module._workerHitTestBoardId = boardId;
387
991
  module._workerHitTestCandidateIds = Array.isArray(data.candidateIds)
@@ -395,6 +999,9 @@
395
999
  ? performance.now()
396
1000
  : Date.now();
397
1001
  module._workerHitTestProfile = data.profile || null;
1002
+ if (this._visibilityBackend === 'native') {
1003
+ window.MindMapNativeCoreBridge?.notifyPrimaryResultAccepted?.(data, roundTripMs);
1004
+ }
398
1005
 
399
1006
  this._flushDeferredHitTestRequest();
400
1007
  }
@@ -402,29 +1009,59 @@
402
1009
  _handleSelectionQueryMessage(data) {
403
1010
  const boardId = this._resolveBoardId(this.module);
404
1011
  const seq = Number(data.seq || 0);
405
- const resolver = this._pendingSelectionResolvers.get(seq) || null;
406
- if (resolver) {
1012
+ const pending = this._pendingSelectionResolvers.get(seq) || null;
1013
+ if (pending) {
407
1014
  this._pendingSelectionResolvers.delete(seq);
1015
+ if (pending.timer !== null) clearTimeout(pending.timer);
408
1016
  }
409
1017
 
410
- if (!resolver) {
1018
+ if (!pending) {
411
1019
  return;
412
1020
  }
413
1021
 
414
- if (seq < Number(this._latestSelectionRequestSeq || 0)) {
415
- resolver(null);
1022
+ if (seq !== Number(this._latestSelectionRequestSeq || 0)) {
1023
+ pending.resolve(null);
416
1024
  return;
417
1025
  }
418
1026
 
419
- if (!boardId || normalizeBoardId(data.boardId) !== boardId) {
420
- resolver(null);
1027
+ const owner = this._classifyWorkerOwner(data, boardId);
1028
+ if (!owner.identityMatches) {
1029
+ pending.resolve(null);
1030
+ if (this._visibilityBackend === 'native' || this._nativeFallbackActive === true) {
1031
+ this._handleVisibilityProtocolFailure('selection result owner identity mismatch');
1032
+ }
1033
+ return;
1034
+ }
1035
+
1036
+ if (data?.stale === true || !owner.revisionMatches) {
1037
+ pending.resolve(null);
1038
+ if (data?.stale === true && owner.revisionMatches &&
1039
+ (this._visibilityBackend === 'native' || this._nativeFallbackActive === true)) {
1040
+ this._handleVisibilityProtocolFailure(
1041
+ this._visibilityBackend === 'native'
1042
+ ? 'native rejected a current selection owner'
1043
+ : 'legacy fallback rejected a current selection owner'
1044
+ );
1045
+ }
1046
+ return;
1047
+ }
1048
+
1049
+ if (!this._validateSelectionResult(data, pending.payload)) {
1050
+ pending.resolve(null);
1051
+ this._handleVisibilityProtocolFailure('invalid selection result payload');
421
1052
  return;
422
1053
  }
423
1054
 
424
1055
  const selectedIds = Array.isArray(data.selectedIds)
425
1056
  ? data.selectedIds.map((nodeId) => String(nodeId || '').trim()).filter(Boolean)
426
1057
  : [];
427
- resolver(selectedIds);
1058
+ if (this._visibilityBackend === 'native') {
1059
+ const roundTripMs = pending.startedAt > 0
1060
+ ? Math.max(0, monotonicNow() - pending.startedAt)
1061
+ : 0;
1062
+ window.MindMapNativeCoreBridge?.notifyPrimaryResultAccepted?.(data, roundTripMs);
1063
+ }
1064
+ pending.resolve(selectedIds);
428
1065
  }
429
1066
 
430
1067
  _handleLodPlanMessage(data) {
@@ -434,7 +1071,7 @@
434
1071
  return;
435
1072
  }
436
1073
 
437
- if (normalizeBoardId(data.boardId) !== boardId) {
1074
+ if (!this._isCurrentWorkerOwner(data, boardId)) {
438
1075
  return;
439
1076
  }
440
1077
 
@@ -442,9 +1079,10 @@
442
1079
  return;
443
1080
  }
444
1081
 
445
- if (String(data.key || '') === this._pendingLodPlanKey) {
446
- this._pendingLodPlanKey = '';
1082
+ if (String(data.key || '') !== this._pendingLodPlanKey) {
1083
+ return;
447
1084
  }
1085
+ this._pendingLodPlanKey = '';
448
1086
 
449
1087
  module.lodRenderer._applyWorkerLodPlanResult?.(data);
450
1088
  }
@@ -453,52 +1091,68 @@
453
1091
  const boardId = this._resolveBoardId(this.module);
454
1092
  const normalizedBoardId = normalizeBoardId(data.boardId);
455
1093
  const seq = Number(data.seq || 0);
456
- const resolver = this._pendingSearchResolvers.get(seq) || null;
457
- if (resolver) {
1094
+ const pending = this._pendingSearchResolvers.get(seq) || null;
1095
+ if (pending) {
458
1096
  this._pendingSearchResolvers.delete(seq);
1097
+ if (pending.timer !== null) clearTimeout(pending.timer);
459
1098
  }
460
1099
 
461
- if (!resolver) {
1100
+ if (!pending) {
462
1101
  return;
463
1102
  }
464
1103
 
465
- if (!boardId || normalizedBoardId !== boardId) {
466
- resolver([]);
1104
+ if (String(data?.type || '') !== 'SEARCH_RESULT' ||
1105
+ data?.stale === true ||
1106
+ !boardId ||
1107
+ normalizedBoardId !== boardId ||
1108
+ !this._isCurrentWorkerOwner(data, boardId, this._searchRevision) ||
1109
+ !Array.isArray(data.hits)) {
1110
+ pending.resolve(null);
467
1111
  return;
468
1112
  }
469
1113
 
470
- const hits = Array.isArray(data.hits)
471
- ? data.hits.map((hit) => ({
1114
+ const hits = data.hits
1115
+ .map((hit) => ({
472
1116
  nodeId: String(hit?.nodeId ?? hit?.id ?? '').trim(),
473
1117
  score: Number(hit?.score || 0)
474
- })).filter((hit) => hit.nodeId)
475
- : [];
476
- resolver(hits);
1118
+ }))
1119
+ .filter((hit) => hit.nodeId && Number.isFinite(hit.score));
1120
+ if (hits.length !== data.hits.length ||
1121
+ hits.some((hit) => !this.module?.nodeObjectsById?.has?.(hit.nodeId))) {
1122
+ pending.resolve(null);
1123
+ return;
1124
+ }
1125
+ pending.resolve(hits);
477
1126
  }
478
1127
 
479
1128
  _resolvePendingSearches(hits) {
480
- const resolvers = Array.from(this._pendingSearchResolvers.values());
1129
+ const pendingQueries = Array.from(this._pendingSearchResolvers.values());
481
1130
  this._pendingSearchResolvers.clear();
482
- for (let i = 0; i < resolvers.length; i++) {
1131
+ for (let i = 0; i < pendingQueries.length; i++) {
483
1132
  try {
484
- resolvers[i](hits);
1133
+ if (pendingQueries[i].timer !== null) clearTimeout(pendingQueries[i].timer);
1134
+ pendingQueries[i].resolve(hits);
485
1135
  } catch {
486
1136
  }
487
1137
  }
488
1138
  }
489
1139
 
490
1140
  _resolvePendingSelectionQueries(nodeIds) {
491
- const resolvers = Array.from(this._pendingSelectionResolvers.values());
1141
+ const pendingQueries = Array.from(this._pendingSelectionResolvers.values());
492
1142
  this._pendingSelectionResolvers.clear();
493
- for (let i = 0; i < resolvers.length; i++) {
1143
+ for (let i = 0; i < pendingQueries.length; i++) {
494
1144
  try {
495
- resolvers[i](nodeIds);
1145
+ if (pendingQueries[i].timer !== null) clearTimeout(pendingQueries[i].timer);
1146
+ pendingQueries[i].resolve(nodeIds);
496
1147
  } catch {
497
1148
  }
498
1149
  }
499
1150
  }
500
1151
 
501
1152
  dispose() {
1153
+ this._disposed = true;
1154
+ this._clearVisibilityReadyTimer();
1155
+ this._resetPendingVisibilityQueries();
502
1156
  try {
503
1157
  this._visibilityWorker?.terminate?.();
504
1158
  } catch { }
@@ -512,35 +1166,29 @@
512
1166
  this._visibilityWorker = null;
513
1167
  this._lodPlanWorker = null;
514
1168
  this._searchWorker = null;
1169
+ this._nativeCoreShadow?.dispose?.();
1170
+ this._nativeCoreShadow = null;
515
1171
  this._visibilityReady = false;
516
1172
  this._lodPlanReady = false;
517
1173
  this._searchReady = false;
518
- this._visibilityQueryPending = false;
519
- this._inFlightVisibilityRequestSeq = 0;
520
- this._deferredVisibilityPayload = null;
521
- this._hitTestQueryPending = false;
522
- this._deferredHitTestPayload = null;
523
- this._resolvePendingSelectionQueries(null);
524
- this._clearHitTestCache(this.module);
525
1174
  this._resolvePendingSearches(null);
1175
+ this._visibilityNodeIds.clear();
1176
+ this._visibilityNodeSnapshots.clear();
1177
+ if (NATIVE_PRIMARY_ENABLED) {
1178
+ window.MindMapNativeCoreBridge?.notifyPrimaryDisposed?.('logic-worker-dispose');
1179
+ }
526
1180
  }
527
1181
 
528
1182
  resetBoard(boardId, reason = 'reset-board') {
529
1183
  const normalizedBoardId = this._resolveBoardId(this.module, boardId);
530
- this._latestVisibilityRequestSeq = 0;
531
- this._latestHitTestRequestSeq = 0;
532
- this._latestSelectionRequestSeq = 0;
533
- this._latestLodPlanRequestSeq = 0;
534
- this._latestSearchRequestSeq = 0;
1184
+ this._boardEpoch++;
1185
+ this._boardRevision = 0;
1186
+ this._searchRevision = 0;
535
1187
  this._pendingLodPlanKey = '';
536
- this._visibilityQueryPending = false;
537
- this._inFlightVisibilityRequestSeq = 0;
538
- this._deferredVisibilityPayload = null;
539
- this._hitTestQueryPending = false;
540
- this._deferredHitTestPayload = null;
541
- this._resolvePendingSelectionQueries(null);
1188
+ this._resetPendingVisibilityQueries();
542
1189
  this._resolvePendingSearches(null);
543
- this._clearHitTestCache(this.module);
1190
+ this._visibilityNodeIds.clear();
1191
+ this._visibilityNodeSnapshots.clear();
544
1192
 
545
1193
  if (this.module) {
546
1194
  this.module._visibilityWorkerPrimed = false;
@@ -552,9 +1200,9 @@
552
1200
  reason,
553
1201
  cellSize: Math.max(1, Number(this.module?.GRID_CELL_SIZE || 500))
554
1202
  };
555
- this._post(this._visibilityWorker, payload);
1203
+ this._postVisibility(payload);
556
1204
  this._post(this._lodPlanWorker, payload);
557
- this._post(this._searchWorker, payload);
1205
+ this._postSearch(payload);
558
1206
  }
559
1207
 
560
1208
  rebuildNodeTables(module = this.module, options = {}) {
@@ -577,6 +1225,10 @@
577
1225
  searchNodes.push(searchSnapshot);
578
1226
  }
579
1227
  });
1228
+ this._boardRevision++;
1229
+ this._searchRevision++;
1230
+ this._visibilityNodeIds = new Set(nodes.map((node) => node.id));
1231
+ this._visibilityNodeSnapshots = new Map(nodes.map((node) => [node.id, node]));
580
1232
 
581
1233
  const payload = {
582
1234
  type: 'REBUILD_BOARD',
@@ -586,9 +1238,9 @@
586
1238
  nodes
587
1239
  };
588
1240
 
589
- const postedVisibility = this._post(this._visibilityWorker, payload);
1241
+ const postedVisibility = this._postVisibility(payload);
590
1242
  const postedLodPlan = this._post(this._lodPlanWorker, payload);
591
- const postedSearch = this._post(this._searchWorker, {
1243
+ const postedSearch = this._postSearch({
592
1244
  type: 'REBUILD_BOARD',
593
1245
  boardId,
594
1246
  reason: String(options.reason || 'rebuild-search-index'),
@@ -622,6 +1274,14 @@
622
1274
  if (!snapshot && !searchSnapshot) {
623
1275
  return false;
624
1276
  }
1277
+ if (snapshot) {
1278
+ this._boardRevision++;
1279
+ this._visibilityNodeIds.add(snapshot.id);
1280
+ this._visibilityNodeSnapshots.set(snapshot.id, snapshot);
1281
+ }
1282
+ if (searchSnapshot) {
1283
+ this._searchRevision++;
1284
+ }
625
1285
 
626
1286
  let postedVisibility = false;
627
1287
  let postedLodPlan = false;
@@ -632,11 +1292,11 @@
632
1292
  boardId,
633
1293
  node: snapshot
634
1294
  };
635
- postedVisibility = this._post(this._visibilityWorker, payload);
1295
+ postedVisibility = this._postVisibility(payload);
636
1296
  postedLodPlan = this._post(this._lodPlanWorker, payload);
637
1297
  }
638
1298
  if (searchSnapshot) {
639
- postedSearch = this._post(this._searchWorker, {
1299
+ postedSearch = this._postSearch({
640
1300
  type: 'UPSERT_NODE',
641
1301
  boardId,
642
1302
  node: searchSnapshot
@@ -654,6 +1314,10 @@
654
1314
  if (!boardId || !normalizedNodeId) {
655
1315
  return false;
656
1316
  }
1317
+ this._boardRevision++;
1318
+ this._searchRevision++;
1319
+ this._visibilityNodeIds.delete(normalizedNodeId);
1320
+ this._visibilityNodeSnapshots.delete(normalizedNodeId);
657
1321
 
658
1322
  const payload = {
659
1323
  type: 'REMOVE_NODE',
@@ -661,9 +1325,9 @@
661
1325
  nodeId: normalizedNodeId
662
1326
  };
663
1327
 
664
- const postedVisibility = this._post(this._visibilityWorker, payload);
1328
+ const postedVisibility = this._postVisibility(payload);
665
1329
  const postedLodPlan = this._post(this._lodPlanWorker, payload);
666
- const postedSearch = this._post(this._searchWorker, payload);
1330
+ const postedSearch = this._postSearch(payload);
667
1331
  if (postedVisibility || postedLodPlan) {
668
1332
  this._clearHitTestCache(module || this.module);
669
1333
  }
@@ -690,7 +1354,8 @@
690
1354
  return false;
691
1355
  }
692
1356
 
693
- return this._post(this._searchWorker, {
1357
+ this._searchRevision++;
1358
+ return this._postSearch({
694
1359
  type: 'BATCH_SYNC',
695
1360
  boardId: normalizedBoardId,
696
1361
  nodes: normalizedNodes,
@@ -766,9 +1431,15 @@
766
1431
  return true;
767
1432
  }
768
1433
 
769
- const posted = this._post(this._visibilityWorker, this._stampVisibilityPayloadForPost(payload));
1434
+ const posted = this._postVisibility(this._stampVisibilityPayloadForPost(payload));
770
1435
  this._visibilityQueryPending = posted;
771
1436
  this._inFlightVisibilityRequestSeq = posted ? seq : 0;
1437
+ if (posted) {
1438
+ this._visibilityQueryStartedAt = monotonicNow();
1439
+ this._armVisibilityQueryTimer(seq);
1440
+ } else if (this._visibilityBackend === 'native') {
1441
+ this._activateLegacyVisibilityFallback('native visibility query post failed');
1442
+ }
772
1443
  return posted;
773
1444
  }
774
1445
 
@@ -779,11 +1450,17 @@
779
1450
 
780
1451
  const payload = this._deferredVisibilityPayload;
781
1452
  this._deferredVisibilityPayload = null;
782
- const posted = this._post(this._visibilityWorker, this._stampVisibilityPayloadForPost(payload));
1453
+ const posted = this._postVisibility(this._stampVisibilityPayloadForPost(payload));
783
1454
  this._visibilityQueryPending = posted;
784
1455
  this._inFlightVisibilityRequestSeq = posted
785
1456
  ? Math.max(0, Math.floor(Number(payload.seq || 0)))
786
1457
  : 0;
1458
+ if (posted) {
1459
+ this._visibilityQueryStartedAt = monotonicNow();
1460
+ this._armVisibilityQueryTimer(this._inFlightVisibilityRequestSeq);
1461
+ } else if (this._visibilityBackend === 'native') {
1462
+ this._activateLegacyVisibilityFallback('deferred native visibility query post failed');
1463
+ }
787
1464
  }
788
1465
 
789
1466
  _describeHitTestQuery(module = this.module, options = {}) {
@@ -797,7 +1474,7 @@
797
1474
  }
798
1475
 
799
1476
  const cellSize = Math.max(1, Number(activeModule.GRID_CELL_SIZE || 500));
800
- const cellRadius = Math.max(0, Math.min(3, Math.round(Number(options.cellRadius || 1))));
1477
+ const cellRadius = Math.max(0, Math.min(3, Math.round(Number(options.cellRadius ?? 1))));
801
1478
  const cellX = Math.floor(pointX / cellSize);
802
1479
  const cellY = Math.floor(pointY / cellSize);
803
1480
 
@@ -841,8 +1518,16 @@
841
1518
  return true;
842
1519
  }
843
1520
 
844
- const posted = this._post(this._visibilityWorker, payload);
1521
+ const posted = this._postVisibility(payload);
845
1522
  this._hitTestQueryPending = posted;
1523
+ this._inFlightHitTestRequestSeq = posted ? payload.seq : 0;
1524
+ this._inFlightHitTestPayload = posted ? { ...payload } : null;
1525
+ if (posted) {
1526
+ this._hitTestQueryStartedAt = monotonicNow();
1527
+ this._armHitTestQueryTimer(payload.seq);
1528
+ } else if (this._visibilityBackend === 'native') {
1529
+ this._activateLegacyVisibilityFallback('native hit-test query post failed');
1530
+ }
846
1531
  return posted;
847
1532
  }
848
1533
 
@@ -853,8 +1538,16 @@
853
1538
 
854
1539
  const payload = this._deferredHitTestPayload;
855
1540
  this._deferredHitTestPayload = null;
856
- const posted = this._post(this._visibilityWorker, payload);
1541
+ const posted = this._postVisibility(payload);
857
1542
  this._hitTestQueryPending = posted;
1543
+ this._inFlightHitTestRequestSeq = posted ? Number(payload.seq || 0) : 0;
1544
+ this._inFlightHitTestPayload = posted ? { ...payload } : null;
1545
+ if (posted) {
1546
+ this._hitTestQueryStartedAt = monotonicNow();
1547
+ this._armHitTestQueryTimer(payload.seq);
1548
+ } else if (this._visibilityBackend === 'native') {
1549
+ this._activateLegacyVisibilityFallback('deferred native hit-test query post failed');
1550
+ }
858
1551
  }
859
1552
 
860
1553
  getCachedHitTestCandidateIds(module = this.module, options = {}) {
@@ -903,12 +1596,18 @@
903
1596
  };
904
1597
 
905
1598
  return new Promise((resolve) => {
906
- this._pendingSelectionResolvers.set(payload.seq, resolve);
907
- const posted = this._post(this._visibilityWorker, payload);
1599
+ const pending = { resolve, payload: { ...payload }, timer: null, startedAt: monotonicNow() };
1600
+ this._pendingSelectionResolvers.set(payload.seq, pending);
1601
+ const posted = this._postVisibility(payload);
908
1602
  if (!posted) {
909
1603
  this._pendingSelectionResolvers.delete(payload.seq);
910
1604
  resolve(null);
1605
+ if (this._visibilityBackend === 'native') {
1606
+ this._activateLegacyVisibilityFallback('native selection query post failed');
1607
+ }
1608
+ return;
911
1609
  }
1610
+ this._armSelectionQueryTimer(payload.seq, pending);
912
1611
  });
913
1612
  }
914
1613
 
@@ -982,12 +1681,25 @@
982
1681
  };
983
1682
 
984
1683
  return new Promise((resolve) => {
985
- this._pendingSearchResolvers.set(seq, resolve);
986
- const posted = this._post(this._searchWorker, payload);
1684
+ const pending = { resolve, timer: null };
1685
+ this._pendingSearchResolvers.set(seq, pending);
1686
+ const posted = this._postSearch(payload);
987
1687
  if (!posted) {
988
1688
  this._pendingSearchResolvers.delete(seq);
989
1689
  resolve(null);
1690
+ return;
990
1691
  }
1692
+ pending.timer = setTimeout(() => {
1693
+ if (!this._pendingSearchResolvers.has(seq)) return;
1694
+ this._pendingSearchResolvers.delete(seq);
1695
+ resolve(null);
1696
+ window.MindCanvasTrace?.emit?.('logicWorker.searchTimeout', {
1697
+ boardId,
1698
+ seq,
1699
+ boardRevision: this._searchRevision,
1700
+ timeoutMs: SEARCH_QUERY_TIMEOUT_MS
1701
+ });
1702
+ }, SEARCH_QUERY_TIMEOUT_MS);
991
1703
  });
992
1704
  }
993
1705
  }