@mindexec/cli 0.2.458 → 0.2.460

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 (41) hide show
  1. package/desktop-workspace-state.cjs +120 -0
  2. package/electron/main.cjs +17 -6
  3. package/electron/source-smoke.mjs +21 -0
  4. package/electron/windows-package-smoke.mjs +8 -2
  5. package/package.json +6 -5
  6. package/remote-fast/osx-arm64/mindexec-remote-fast.dll +0 -0
  7. package/remote-fast/osx-x64/mindexec-remote-fast.dll +0 -0
  8. package/remote-fast/win-x64/mindexec-remote-fast.dll +0 -0
  9. package/scripts/desktop-workspace-state-smoke.mjs +81 -0
  10. package/server.js +36 -16
  11. package/wwwroot/_content/MindExecution.Shared/js/mind-map-animated-image-preview.js +270 -0
  12. package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +106 -60
  13. package/wwwroot/_content/MindExecution.Shared/js/mind-map-interactions.js +30 -26
  14. package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-renderer.js +94 -124
  15. package/wwwroot/_content/MindExecution.Shared/js/mind-map-menu-manager.js +43 -21
  16. package/wwwroot/_content/MindExecution.Shared/js/mind-map-nodes.js +45 -13
  17. package/wwwroot/_content/MindExecution.Shared/js/mind-map-render-plan.js +117 -1
  18. package/wwwroot/_content/MindExecution.Shared/js/mind-map-texture-factory.js +4 -1
  19. package/wwwroot/_framework/{MindExecution.Core.2ch68iyy8o.dll → MindExecution.Core.oqju650dkd.dll} +0 -0
  20. package/wwwroot/_framework/{MindExecution.Kernel.dh617xfv36.dll → MindExecution.Kernel.7zjugdfmfg.dll} +0 -0
  21. package/wwwroot/_framework/{MindExecution.Plugins.Admin.0sm50hbae9.dll → MindExecution.Plugins.Admin.qln7lkmsnn.dll} +0 -0
  22. package/wwwroot/_framework/{MindExecution.Plugins.Business.3wq01orrbu.dll → MindExecution.Plugins.Business.rd6flxuebm.dll} +0 -0
  23. package/wwwroot/_framework/{MindExecution.Plugins.Concept.z8yl28fa2a.dll → MindExecution.Plugins.Concept.0qrgx3epss.dll} +0 -0
  24. package/wwwroot/_framework/{MindExecution.Plugins.Directory.m7murp5oes.dll → MindExecution.Plugins.Directory.4prauy9d1z.dll} +0 -0
  25. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.yuyrbpf0vh.dll → MindExecution.Plugins.PlanMaster.cobfta1p3l.dll} +0 -0
  26. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.rwxvn00rm2.dll → MindExecution.Plugins.YouTube.99bahbgkkr.dll} +0 -0
  27. package/wwwroot/_framework/{MindExecution.Shared.r9k48iyijb.dll → MindExecution.Shared.sevaa4rgkp.dll} +0 -0
  28. package/wwwroot/_framework/{MindExecution.Web.742aribkxm.dll → MindExecution.Web.coqh2ccnuk.dll} +0 -0
  29. package/wwwroot/_framework/blazor.boot.json +21 -21
  30. package/wwwroot/app-icon-1024.png +0 -0
  31. package/wwwroot/apple-touch-icon.png +0 -0
  32. package/wwwroot/appsettings.json +81 -81
  33. package/wwwroot/favicon-32x32.png +0 -0
  34. package/wwwroot/favicon.ico +0 -0
  35. package/wwwroot/icon-192.png +0 -0
  36. package/wwwroot/icon-512.png +0 -0
  37. package/wwwroot/index.html +70 -46
  38. package/wwwroot/manifest.webmanifest +4 -4
  39. package/wwwroot/mindexec-favicon-v3.png +0 -0
  40. package/wwwroot/service-worker-assets.js +888 -880
  41. package/wwwroot/service-worker.js +1 -1
@@ -682,13 +682,32 @@ window.MindMapInteractions = (function () {
682
682
  module._lastNativePasteEventAt = performance.now();
683
683
  module._pendingClipboardPasteFallbackToken = null;
684
684
  console.log('[MindMap] Ctrl+V fallback read clipboard text. Creating node through PasteTextFromClipboard.');
685
- await module.dotNetHelper.invokeMethodAsync('PasteTextFromClipboard', text, cursorX, cursorY);
685
+ const createdNode = await module.dotNetHelper.invokeMethodAsync('PasteTextFromClipboard', text, cursorX, cursorY);
686
+ completePastedTextNode(module, createdNode);
686
687
  } catch (error) {
687
688
  console.warn('[MindMap] Ctrl+V fallback clipboard read failed:', error);
688
689
  }
689
690
  }, 120);
690
691
  }
691
692
 
693
+ function completePastedTextNode(module, createdNode) {
694
+ const nodeId = String(createdNode?.nodeId ?? createdNode?.NodeId ?? '').trim();
695
+ const nodeEntry = nodeId ? module?.nodeObjectsById?.get?.(nodeId) : null;
696
+ if (!nodeEntry) return false;
697
+
698
+ finalizeSingleSelection(module, nodeId, { notifyBlazor: true, bringToFront: true, showMenu: true });
699
+ module.pendingSelectedActivationNodeIds?.clear?.();
700
+ module.pendingSelectedActivationNodeIds?.add?.(nodeId);
701
+ MindMapNodes.moveCursorAfterNodePlacement?.(
702
+ module,
703
+ nodeEntry.model,
704
+ nodeEntry.model?.width,
705
+ nodeEntry.model?.height,
706
+ { moveCamera: false }
707
+ );
708
+ return true;
709
+ }
710
+
692
711
  function copyTextMetricsFromElement(target, source) {
693
712
  if (!target || !source || typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') {
694
713
  return;
@@ -2791,30 +2810,14 @@ window.MindMapInteractions = (function () {
2791
2810
  if (newNodeIds.length === 1) {
2792
2811
  module.selectedNodeIdJs = newNodeIds[0];
2793
2812
 
2794
- // Auto-move cursor when pasting a single node
2795
- const pastedNode = nodesToPaste[0];
2796
- const PADDING = 30;
2797
- const direction = module.cursorDirection || 'horizontal';
2798
- const nodeX = pastedNode.PositionX;
2799
- const nodeY = pastedNode.PositionY;
2800
- const width = pastedNode.Width || 400;
2801
- const height = pastedNode.Height || 200;
2802
-
2803
- let nextCursorX, nextCursorY;
2804
- if (direction === 'vertical') {
2805
- // Move down (node height + padding)
2806
- nextCursorX = nodeX;
2807
- nextCursorY = nodeY - height - PADDING;
2808
- } else {
2809
- // Move right (node width + padding)
2810
- nextCursorX = nodeX + width + PADDING;
2811
- nextCursorY = nodeY;
2812
- }
2813
-
2814
- if (typeof module.updateCursorPosition === 'function') {
2815
- module.updateCursorPosition(nextCursorX, nextCursorY, false);
2816
- console.log(`[MindMap] 📍 Cursor moved to (${nextCursorX.toFixed(0)}, ${nextCursorY.toFixed(0)}) after paste`);
2817
- }
2813
+ const pastedEntry = module.nodeObjectsById?.get?.(newNodeIds[0]);
2814
+ MindMapNodes.moveCursorAfterNodePlacement?.(
2815
+ module,
2816
+ pastedEntry?.model || nodesToPaste[0],
2817
+ pastedEntry?.model?.width || nodesToPaste[0].Width,
2818
+ pastedEntry?.model?.height || nodesToPaste[0].Height,
2819
+ { moveCamera: false }
2820
+ );
2818
2821
  }
2819
2822
  if (module.pendingSelectedActivationNodeIds instanceof Set) {
2820
2823
  module.pendingSelectedActivationNodeIds.clear();
@@ -2857,7 +2860,8 @@ window.MindMapInteractions = (function () {
2857
2860
  if (module.dotNetHelper) {
2858
2861
  const cursorX = module.cursorPosition?.x ?? 0;
2859
2862
  const cursorY = module.cursorPosition?.y ?? 0;
2860
- module.dotNetHelper.invokeMethodAsync('PasteTextFromClipboard', finalText, cursorX, cursorY);
2863
+ const createdNode = await module.dotNetHelper.invokeMethodAsync('PasteTextFromClipboard', finalText, cursorX, cursorY);
2864
+ completePastedTextNode(module, createdNode);
2861
2865
  }
2862
2866
  }
2863
2867
  }
@@ -404,65 +404,24 @@
404
404
  contentType === 'embed';
405
405
  }
406
406
 
407
- function isAnimatedGifImageUrl(url) {
408
- const normalized = normalizeImageAssetUrl(url);
409
- if (!normalized) return false;
410
- return normalized.split(/[?#]/)[0].toLowerCase().endsWith('.gif');
407
+ function isAnimatedGifImageModel(model) {
408
+ return window.MindMapAnimatedImagePreview?.isAnimatedGifImageModel?.(model) === true;
411
409
  }
412
410
 
413
- function getMetadataValueCaseInsensitive(metadata, ...keys) {
414
- if (!metadata || typeof metadata !== 'object') return '';
415
-
416
- for (const key of keys) {
417
- const direct = metadata[key];
418
- if (direct !== undefined && direct !== null && String(direct).trim()) {
419
- return String(direct).trim();
420
- }
421
- }
422
-
423
- const wantedKeys = new Set(keys.map(key => String(key || '').toLowerCase()));
424
- for (const key of Object.keys(metadata)) {
425
- if (wantedKeys.has(String(key || '').toLowerCase())) {
426
- const value = metadata[key];
427
- if (value !== undefined && value !== null && String(value).trim()) {
428
- return String(value).trim();
429
- }
430
- }
431
- }
432
-
433
- return '';
411
+ function isWebpImageModel(model) {
412
+ return window.MindMapAnimatedImagePreview?.isWebpImageModel?.(model) === true;
434
413
  }
435
414
 
436
- function isAnimatedGifImageModel(model) {
415
+ function isAnimationCapableImageModel(model) {
437
416
  if (!model || getNodeContentType(model) !== 'image') {
438
417
  return false;
439
418
  }
440
419
 
441
- if (window.MindMapNodes?.isAnimatedGifImageNodeModel) {
442
- return window.MindMapNodes.isAnimatedGifImageNodeModel(model) === true;
443
- }
444
-
445
- const metadata = getNodeMetadata(model) || {};
446
- const mimeType = getMetadataValueCaseInsensitive(
447
- metadata,
448
- 'fileMime',
449
- 'FileMime',
450
- 'mimeType',
451
- 'MimeType'
452
- ).toLowerCase();
453
- if (mimeType === 'image/gif') {
454
- return true;
420
+ if (window.MindMapNodes?.isAnimationCapableImageNodeModel) {
421
+ return window.MindMapNodes.isAnimationCapableImageNodeModel(model) === true;
455
422
  }
456
423
 
457
- return [
458
- model?.response,
459
- model?.Response,
460
- metadata?.OriginalPath,
461
- metadata?.OriginalUrl,
462
- metadata?.originalUrl,
463
- metadata?.ImageUrl,
464
- metadata?.imageUrl
465
- ].some(isAnimatedGifImageUrl);
424
+ return isAnimatedGifImageModel(model) || isWebpImageModel(model);
466
425
  }
467
426
 
468
427
  function isCss3dRendererEnabled(module) {
@@ -493,40 +452,8 @@
493
452
  || normalized.startsWith('file:');
494
453
  }
495
454
 
496
- function getCanonicalImageAssetKey(url) {
497
- const normalized = normalizeImageAssetUrl(url);
498
- if (!normalized) {
499
- return '';
500
- }
501
-
502
- const withoutQuery = normalized.split(/[?#]/)[0];
503
- const lower = withoutQuery.toLowerCase();
504
- const thumbsMarker = '/assets/thumbs/';
505
- const assetsMarker = '/assets/';
506
-
507
- if (lower.includes(thumbsMarker)) {
508
- const assetName = withoutQuery.slice(lower.lastIndexOf(thumbsMarker) + thumbsMarker.length).split('/').pop() || '';
509
- const dotIndex = assetName.lastIndexOf('.');
510
- return (dotIndex > 0 ? assetName.slice(0, dotIndex) : assetName).toLowerCase();
511
- }
512
-
513
- if (lower.includes(assetsMarker)) {
514
- const assetName = withoutQuery.slice(lower.lastIndexOf(assetsMarker) + assetsMarker.length).split('/').pop() || '';
515
- const dotIndex = assetName.lastIndexOf('.');
516
- return (dotIndex > 0 ? assetName.slice(0, dotIndex) : assetName).toLowerCase();
517
- }
518
-
519
- return withoutQuery.toLowerCase();
520
- }
521
-
522
455
  function areImageAssetUrlsEquivalent(leftUrl, rightUrl) {
523
- const leftKey = getCanonicalImageAssetKey(leftUrl);
524
- const rightKey = getCanonicalImageAssetKey(rightUrl);
525
- if (leftKey && rightKey) {
526
- return leftKey === rightKey;
527
- }
528
-
529
- return normalizeImageAssetUrl(leftUrl) === normalizeImageAssetUrl(rightUrl);
456
+ return window.MindMapAnimatedImagePreview?.areAssetUrlsEquivalent?.(leftUrl, rightUrl) === true;
530
457
  }
531
458
 
532
459
  function isThumbnailImageUrl(url) {
@@ -557,9 +484,9 @@
557
484
  const missingAssetUrl = normalizeImageAssetUrl(metadata?.MissingAssetUrl || '');
558
485
  const responseFullResUrl = isImageContent && !isThumbnailImageUrl(responseUrl) ? responseUrl : '';
559
486
  const missingFullResUrl = !isThumbnailImageUrl(missingAssetUrl) ? missingAssetUrl : '';
560
- const animatedGif = isAnimatedGifImageModel(model);
561
- const mediaPreviewUrl = animatedGif ? '' : (thumbnailUrl || snapshotUrl || previewImageUrl);
562
- const responsePreviewUrl = isImageContent && !animatedGif ? responseUrl : '';
487
+ const animationCapable = isAnimationCapableImageModel(model);
488
+ const mediaPreviewUrl = animationCapable ? '' : (thumbnailUrl || snapshotUrl || previewImageUrl);
489
+ const responsePreviewUrl = isImageContent && !animationCapable ? responseUrl : '';
563
490
  const previewUrl = mediaPreviewUrl || responsePreviewUrl || originalPath || originalUrl || missingAssetUrl;
564
491
  const fullResUrl = isImageContent
565
492
  ? (originalUrl || originalPath || responseFullResUrl || missingFullResUrl)
@@ -575,7 +502,8 @@
575
502
  missingAssetUrl,
576
503
  previewUrl,
577
504
  fullResUrl,
578
- animatedGif
505
+ animationCapable,
506
+ animatedGif: animationCapable
579
507
  };
580
508
  }
581
509
 
@@ -818,6 +746,8 @@
818
746
  this._imageAtlasLoadingIds = new Set();
819
747
  this._imageAtlasActiveLoads = 0;
820
748
  this._imageAtlasRetryAfter = new Map();
749
+ this._residentStaticPreviewUrls = new Map();
750
+ this._residentStaticPreviewEpoch = 0;
821
751
  this._imageAtlasBatchPendingIds = new Set();
822
752
  this._imageAtlasBatchReadyIds = new Set();
823
753
  this._imageAtlasBatchActive = false;
@@ -1115,6 +1045,7 @@
1115
1045
  if (this._isNodeArrayDirty || this._cachedNodeArray.length !== nodeObjectsById.size) {
1116
1046
  this._cachedNodeArray = Array.from(nodeObjectsById.values());
1117
1047
  this._isNodeArrayDirty = false;
1048
+ window.MindMapRenderPlan?.prepareImageAtlasForEntries?.(this, this._cachedNodeArray);
1118
1049
  }
1119
1050
  }
1120
1051
  // ▲▲▲ [Optimization] ▲▲▲
@@ -1379,14 +1310,19 @@
1379
1310
  return;
1380
1311
  }
1381
1312
 
1313
+ const residentPreviewUrl = this._getResidentStaticPreviewUrl(nodeId, entry.model, assetUrls);
1314
+
1382
1315
  const cachedRecord = window.MindMapNodes?.imageCache?.get?.(nodeId) || null;
1383
- const cachedImage = window.MindMapTextureFactory?.resolveCachedImageEntry?.(cachedRecord, assetUrls.previewUrl, {
1384
- preferOriginal: false,
1385
- allowOriginalReuseForPreview: false,
1386
- originalUrl: assetUrls.fullResUrl || assetUrls.originalUrl || ''
1387
- }) || cachedRecord;
1316
+ const cachedImage = residentPreviewUrl
1317
+ ? (window.MindMapTextureFactory?.resolveCachedImageEntry?.(cachedRecord, residentPreviewUrl, {
1318
+ preferOriginal: false,
1319
+ allowOriginalReuseForPreview: false,
1320
+ resizeWidth: IMAGE_ATLAS_PREVIEW_REQUEST_WIDTH,
1321
+ originalUrl: assetUrls.fullResUrl || assetUrls.originalUrl || ''
1322
+ }) || null)
1323
+ : null;
1388
1324
  const cachedImageUrl = normalizeImageAssetUrl(cachedImage?.url || '');
1389
- const requestedPreviewUrl = normalizeImageAssetUrl(assetUrls.previewUrl);
1325
+ const requestedPreviewUrl = normalizeImageAssetUrl(residentPreviewUrl);
1390
1326
  const cachedRetryAfter = Number(cachedImage?.retryAfter || 0);
1391
1327
  const cachedAssetUrl = cachedImageUrl || String(cachedImage?.assetKey || '');
1392
1328
  if (
@@ -1403,11 +1339,28 @@
1403
1339
  nodeId,
1404
1340
  model: entry.model,
1405
1341
  previewUrl: assetUrls.previewUrl,
1406
- originalUrl: assetUrls.fullResUrl || assetUrls.originalUrl || ''
1342
+ thumbnailUrl: assetUrls.thumbnailUrl || '',
1343
+ originalUrl: assetUrls.fullResUrl || assetUrls.originalUrl || '',
1344
+ previewEpoch: this._residentStaticPreviewEpoch
1407
1345
  });
1408
1346
  this._pumpImageAtlasPreviewLoadQueue();
1409
1347
  }
1410
1348
 
1349
+ _getResidentStaticPreviewUrl(nodeId, model, assetUrls = getImageModelAssetUrls(model)) {
1350
+ return window.MindMapAnimatedImagePreview?.getResidentStaticPreviewUrl?.(
1351
+ this, nodeId, model, assetUrls
1352
+ ) || '';
1353
+ }
1354
+
1355
+ async _resolveSafeResidentPreviewUrl(item) {
1356
+ const assetUrls = getImageModelAssetUrls(item?.model);
1357
+ const resolver = window.MindMapAnimatedImagePreview?.resolveSafeResidentPreviewUrl;
1358
+ return typeof resolver === 'function'
1359
+ ? await resolver(this, item, assetUrls, getImageModelAssetUrls, details =>
1360
+ emitMindCanvasTrace('lod.image.staticPreview.ready', this._module, details))
1361
+ : '';
1362
+ }
1363
+
1411
1364
  _pumpImageAtlasPreviewLoadQueue() {
1412
1365
  const textureFactory = window.MindMapTextureFactory;
1413
1366
  const imageCache = window.MindMapNodes?.imageCache;
@@ -1440,8 +1393,13 @@
1440
1393
  this._imageAtlasLoadingIds.add(item.nodeId);
1441
1394
  let didLoadImage = false;
1442
1395
 
1443
- Promise.resolve(
1444
- textureFactory.ensureImageCached(item.nodeId, item.previewUrl, imageCache, {
1396
+ Promise.resolve(this._resolveSafeResidentPreviewUrl(item)).then((resolvedPreviewUrl) => {
1397
+ item.resolvedPreviewUrl = resolvedPreviewUrl;
1398
+ if (!resolvedPreviewUrl) {
1399
+ return null;
1400
+ }
1401
+
1402
+ return textureFactory.ensureImageCached(item.nodeId, resolvedPreviewUrl, imageCache, {
1445
1403
  authToken: this._module?.authToken || '',
1446
1404
  resizeWidth: IMAGE_ATLAS_PREVIEW_REQUEST_WIDTH,
1447
1405
  preferOriginal: false,
@@ -1463,12 +1421,18 @@
1463
1421
  setImageModelPreviewUrl(item.model, normalized);
1464
1422
  }
1465
1423
  }
1466
- })
1467
- ).then((cachedImage) => {
1424
+ });
1425
+ }).then((cachedImage) => {
1468
1426
  didLoadImage = !!(cachedImage?.image) && cachedImage?.isError !== true;
1469
1427
  item.cachedImage = cachedImage || null;
1470
- }).catch(() => {
1428
+ }).catch((error) => {
1471
1429
  didLoadImage = false;
1430
+ if (isWebpImageModel(item?.model)) {
1431
+ emitMindCanvasTrace('lod.image.staticPreview.failed', this._module, {
1432
+ nodeId: item.nodeId,
1433
+ reason: String(error?.message || error || 'unknown')
1434
+ });
1435
+ }
1472
1436
  }).finally(() => {
1473
1437
  this._imageAtlasActiveLoads = Math.max(0, this._imageAtlasActiveLoads - 1);
1474
1438
  this._imageAtlasQueuedIds.delete(item.nodeId);
@@ -1511,14 +1475,22 @@
1511
1475
  _getImageLodSource(entry) {
1512
1476
  const nodeId = String(entry?.model?.id || '').trim();
1513
1477
  const assetUrls = getImageModelAssetUrls(entry?.model);
1478
+ const webpModel = isWebpImageModel(entry?.model);
1479
+ const residentPreviewUrl = this._getResidentStaticPreviewUrl(nodeId, entry?.model, assetUrls);
1480
+ if (webpModel && !residentPreviewUrl) {
1481
+ this._queueImageAtlasPreviewLoad(entry);
1482
+ return null;
1483
+ }
1484
+
1514
1485
  const cachedRecord = nodeId ? window.MindMapNodes?.imageCache?.get?.(nodeId) || null : null;
1515
1486
  const cachedImage = nodeId
1516
1487
  ? (
1517
- window.MindMapTextureFactory?.resolveCachedImageEntry?.(cachedRecord, assetUrls.previewUrl, {
1488
+ window.MindMapTextureFactory?.resolveCachedImageEntry?.(cachedRecord, residentPreviewUrl, {
1518
1489
  preferOriginal: false,
1519
1490
  allowOriginalReuseForPreview: false,
1491
+ resizeWidth: IMAGE_ATLAS_PREVIEW_REQUEST_WIDTH,
1520
1492
  originalUrl: assetUrls.fullResUrl || assetUrls.originalUrl || ''
1521
- }) || cachedRecord
1493
+ }) || null
1522
1494
  )
1523
1495
  : null;
1524
1496
  const cachedSource = cachedImage?.image || null;
@@ -1540,6 +1512,13 @@
1540
1512
  };
1541
1513
  }
1542
1514
 
1515
+ if (isThumbnailImageUrl(residentPreviewUrl)) {
1516
+ if (!isCachedImageRetryPending) {
1517
+ this._queueImageAtlasPreviewLoad(entry);
1518
+ }
1519
+ return null;
1520
+ }
1521
+
1543
1522
  const cssImage = entry?.cssObject?.element?.querySelector?.('img') || null;
1544
1523
  const cssImageWidth = Number(cssImage?.naturalWidth || cssImage?.width || 0);
1545
1524
  const cssImageHeight = Number(cssImage?.naturalHeight || cssImage?.height || 0);
@@ -1640,8 +1619,6 @@
1640
1619
  sy = (source.height - sh) * 0.5;
1641
1620
  }
1642
1621
 
1643
- // Match NEAR image nodes: CSS/WebGL both use object-fit: cover
1644
- // against the node rectangle, then map that result over the quad.
1645
1622
  scratchCtx.drawImage(source.image, sx, sy, sw, sh, 0, 0, size, size);
1646
1623
  scratchCtx.restore();
1647
1624
 
@@ -1652,9 +1629,8 @@
1652
1629
  ctx.fillStyle = '#eef2f7';
1653
1630
  ctx.fillRect(x, y, size, size);
1654
1631
  ctx.drawImage(scratch, 0, 0, size, size, x, y, size, size);
1655
- if (this.imageAtlasTexture) {
1656
- this.imageAtlasTexture.needsUpdate = true;
1657
- }
1632
+ if (!window.MindMapRenderPlan?.uploadImageAtlasTile?.(this, slot, scratch))
1633
+ window.MindMapRenderPlan?.scheduleImageAtlasUpload?.(this, this._module);
1658
1634
  return true;
1659
1635
  } catch {
1660
1636
  try { scratchCtx.restore(); } catch { }
@@ -3417,7 +3393,7 @@
3417
3393
  const pendingAnimatedGifCssFallbacks = usesAnimatedGifCssFallbacks && activeImageCssFallbackCount <= 0;
3418
3394
  const staleImageCssFallbacks = imageCssFallbackCount > 0 &&
3419
3395
  (usesAnyImageCssFallbacks !== true || activeImageCssFallbackCount <= 0);
3420
- const lineDetailRestorePending = this._hasPendingLineDetailRestore(lodBand);
3396
+ const lineDetailRestorePending = window.MindMapRenderPlan?.hasActionableResidentLineDetails?.(this, module, lodBand) === true;
3421
3397
  const dirty = (
3422
3398
  this._instancesDirty === true ||
3423
3399
  this._imageInstancesDirty === true ||
@@ -5878,9 +5854,7 @@
5878
5854
  if (this.isInLODMode === true &&
5879
5855
  this._instancesDirty &&
5880
5856
  this._shouldDeferResidentInstanceRebuild(module, lodBand)) {
5881
- if (module) {
5882
- module._forceUpdateFrames = Math.max(Number(module._forceUpdateFrames || 0), 12);
5883
- }
5857
+ this._keepResidentLodSettleFrames(module, 2, 'defer-resident-instance-rebuild');
5884
5858
  if (LOD_PERF_DEBUG) {
5885
5859
  _prof('done');
5886
5860
  }
@@ -5896,9 +5870,7 @@
5896
5870
  if (this.isInLODMode === true &&
5897
5871
  this._lodCleanupPending === true &&
5898
5872
  this._isResidentLodInteractionActive(module)) {
5899
- if (module) {
5900
- module._forceUpdateFrames = Math.max(Number(module._forceUpdateFrames || 0), 12);
5901
- }
5873
+ this._keepResidentLodSettleFrames(module, 2, 'defer-lod-cleanup');
5902
5874
  if (LOD_PERF_DEBUG) {
5903
5875
  _prof('done');
5904
5876
  }
@@ -6174,9 +6146,7 @@
6174
6146
  // ▼▼▼ [Perf] Only update instance data when dirty or node count changed ▼▼▼
6175
6147
  // Check if node count changed (add/remove detection)
6176
6148
  if (this._instancesDirty && this._shouldDeferResidentInstanceRebuild(module, lodBand)) {
6177
- if (module) {
6178
- module._forceUpdateFrames = Math.max(Number(module._forceUpdateFrames || 0), 12);
6179
- }
6149
+ this._keepResidentLodSettleFrames(module, 2, 'defer-resident-instance-rebuild');
6180
6150
  if (LOD_PERF_DEBUG) {
6181
6151
  _prof('done');
6182
6152
  }
@@ -6388,9 +6358,7 @@
6388
6358
  Number(this._residentRebuildDeferredUntil || 0),
6389
6359
  now + LOD_RESIDENT_REBUILD_SETTLE_MS
6390
6360
  );
6391
- if (module) {
6392
- module._forceUpdateFrames = Math.max(Number(module._forceUpdateFrames || 0), 4);
6393
- }
6361
+ this._keepResidentLodSettleFrames(module, 2, 'defer-resident-full-rebuild');
6394
6362
  }
6395
6363
 
6396
6364
  if (this._instancesDirty && residentFullBuildBlockedThisFrame !== true) {
@@ -7119,13 +7087,8 @@
7119
7087
  }, safeDelayMs);
7120
7088
  }
7121
7089
 
7122
- _keepResidentLodSettleFrames(module = this._module, frames = 12, reason = 'resident-settle') {
7090
+ _keepResidentLodSettleFrames(module = this._module, _frames = 12, reason = 'resident-settle') {
7123
7091
  if (module) {
7124
- if (this._isResidentLodInteractionActive(module)) {
7125
- module._forceUpdateFrames = Math.max(Number(module._forceUpdateFrames || 0), frames);
7126
- return;
7127
- }
7128
-
7129
7092
  this._scheduleResidentLodWake(reason, LOD_RESIDENT_REBUILD_SETTLE_MS);
7130
7093
  }
7131
7094
  }
@@ -9504,6 +9467,7 @@
9504
9467
  clearTimeout(this._residentLodWakeTimer);
9505
9468
  this._residentLodWakeTimer = 0;
9506
9469
  }
9470
+ window.MindMapRenderPlan?.clearScheduledImageAtlasUpload?.(this);
9507
9471
  this._residentLodWakeReason = '';
9508
9472
  this.isInLODMode = null;
9509
9473
 
@@ -9550,6 +9514,8 @@
9550
9514
  this._imageAtlasLoadingIds.clear();
9551
9515
  this._imageAtlasActiveLoads = 0;
9552
9516
  this._imageAtlasRetryAfter.clear();
9517
+ this._residentStaticPreviewUrls.clear();
9518
+ this._residentStaticPreviewEpoch++;
9553
9519
  this._resetImageAtlasBatchState();
9554
9520
  this._imageAtlasRebuildPending = false;
9555
9521
  this._imageAtlasRebuildDeferredUntil = 0;
@@ -9669,6 +9635,7 @@
9669
9635
  this._nearPriorityRecordById?.delete?.(nodeId);
9670
9636
  this._lodConsoleProxyNodeIds?.delete?.(nodeId);
9671
9637
  this._lodImageCssFallbackNodeIds.delete(nodeId);
9638
+ this._residentStaticPreviewUrls.delete(nodeId);
9672
9639
  this._lodTemplateCssNodeIds?.delete?.(nodeId);
9673
9640
  this._fullResCssFringePreparedIds?.delete?.(nodeId);
9674
9641
  removedAny = true;
@@ -10253,6 +10220,8 @@
10253
10220
  this._imageAtlasLoadingIds.clear();
10254
10221
  this._imageAtlasActiveLoads = 0;
10255
10222
  this._imageAtlasRetryAfter.clear();
10223
+ this._residentStaticPreviewUrls.clear();
10224
+ this._residentStaticPreviewEpoch++;
10256
10225
  this._pendingSelectionRefreshIds.clear();
10257
10226
  if (this._idleRebuildTimer) {
10258
10227
  clearTimeout(this._idleRebuildTimer);
@@ -10262,6 +10231,7 @@
10262
10231
  clearTimeout(this._residentLodWakeTimer);
10263
10232
  this._residentLodWakeTimer = 0;
10264
10233
  }
10234
+ window.MindMapRenderPlan?.clearScheduledImageAtlasUpload?.(this);
10265
10235
  this._resetImageAtlasBatchState();
10266
10236
  this.thumbnailMap.clear();
10267
10237
  this.pendingNodes.clear();
@@ -1061,27 +1061,31 @@
1061
1061
  }
1062
1062
 
1063
1063
  // ▼▼▼ [New] Screen-space position using worldToScreen ▼▼▼
1064
- function worldToScreen(worldX, worldY, camera, container) {
1065
- const THREE = globalThis.THREE;
1066
- const vector = new THREE.Vector3(worldX, worldY, 0);
1067
- vector.project(camera);
1068
-
1069
- const halfWidth = container.clientWidth / 2;
1070
- const halfHeight = container.clientHeight / 2;
1071
-
1072
- return {
1073
- x: (vector.x * halfWidth) + halfWidth,
1074
- y: -(vector.y * halfHeight) + halfHeight
1075
- };
1076
- }
1077
-
1078
- function updateMenuScreenPosition(menuEl, nodeEntry) {
1064
+ function worldToScreen(worldX, worldY, camera, container) {
1065
+ const THREE = globalThis.THREE;
1066
+ const vector = new THREE.Vector3(worldX, worldY, 0);
1067
+ vector.project(camera);
1068
+
1069
+ const viewportWidth = Number(_module?._lastViewportWidth || _module?._lastContainerWidth || container.clientWidth || 1);
1070
+ const viewportHeight = Number(_module?._lastViewportHeight || _module?._lastContainerHeight || container.clientHeight || 1);
1071
+ const viewportLeft = Number(_module?._lastViewportLeft || 0);
1072
+ const viewportTop = Number(_module?._lastViewportTop || 0);
1073
+ const halfWidth = viewportWidth / 2;
1074
+ const halfHeight = viewportHeight / 2;
1075
+
1076
+ return {
1077
+ x: viewportLeft + (vector.x * halfWidth) + halfWidth,
1078
+ y: viewportTop - (vector.y * halfHeight) + halfHeight
1079
+ };
1080
+ }
1081
+
1082
+ function updateMenuScreenPosition(menuEl, nodeEntry, cameraMotionOnly = false) {
1079
1083
  if (!menuEl || !nodeEntry || !_module || !_module.camera || !_module.container) return;
1080
1084
 
1081
1085
  const templateNode = document.getElementById(`node-${nodeEntry.model?.id || currentMenuNodeId}`);
1082
1086
  let anchorX = null;
1083
1087
  let anchorY = null;
1084
- const shouldPreferProjectedAnchor =
1088
+ const shouldPreferProjectedAnchor = cameraMotionOnly === true ||
1085
1089
  _module.isZooming === true ||
1086
1090
  _module.isPanning === true ||
1087
1091
  _module.isDraggingNode === true ||
@@ -1116,8 +1120,15 @@
1116
1120
  anchorY = screenPos.y;
1117
1121
  }
1118
1122
 
1119
- const menuRect = menuEl.getBoundingClientRect();
1120
- const halfMenuWidth = menuRect.width > 0 ? menuRect.width / 2 : 0;
1123
+ let halfMenuWidth = Number(menuEl._mindMapHalfWidth || 0);
1124
+ let menuHeight = Number(menuEl._mindMapHeight || 0);
1125
+ if (cameraMotionOnly !== true || halfMenuWidth <= 0 || menuHeight <= 0) {
1126
+ const menuRect = menuEl.getBoundingClientRect();
1127
+ halfMenuWidth = menuRect.width > 0 ? menuRect.width / 2 : 0;
1128
+ menuHeight = menuRect.height || 0;
1129
+ menuEl._mindMapHalfWidth = halfMenuWidth;
1130
+ menuEl._mindMapHeight = menuHeight;
1131
+ }
1121
1132
  const viewportMargin = 10;
1122
1133
  const verticalGap = 14;
1123
1134
 
@@ -1126,7 +1137,7 @@
1126
1137
  Math.max(viewportMargin + halfMenuWidth, anchorX)
1127
1138
  );
1128
1139
  const anchoredTop = anchorY - verticalGap;
1129
- const minTop = viewportMargin + (menuRect.height || 0);
1140
+ const minTop = viewportMargin + menuHeight;
1130
1141
  const clampedTop = Math.max(minTop, anchoredTop);
1131
1142
 
1132
1143
  menuEl.style.left = `${clampedLeft}px`;
@@ -1134,7 +1145,7 @@
1134
1145
  }
1135
1146
  // ▲▲▲ [New] ▲▲▲
1136
1147
 
1137
- function update() {
1148
+ function update() {
1138
1149
  if (currentMenuElement && currentMenuNodeId && _module) {
1139
1150
  const nodeEntry = _module.nodeObjectsById.get(currentMenuNodeId);
1140
1151
  if (nodeEntry) {
@@ -1543,8 +1554,19 @@
1543
1554
  });
1544
1555
  }
1545
1556
  }
1557
+
1558
+ function updateForCameraMotion() {
1559
+ if (!currentMenuElement || !currentMenuNodeId || !_module) return false;
1560
+ const nodeEntry = _module.nodeObjectsById.get(currentMenuNodeId);
1561
+ if (!nodeEntry) {
1562
+ hideMenu();
1563
+ return false;
1564
+ }
1565
+ updateMenuScreenPosition(currentMenuElement, nodeEntry, true);
1566
+ return true;
1567
+ }
1546
1568
 
1547
- window.MindMapMenuManager = { init, showMenu, hideMenu, update };
1569
+ window.MindMapMenuManager = { init, showMenu, hideMenu, update, updateForCameraMotion };
1548
1570
 
1549
1571
  console.log('✅ mind-map-menu-manager.js loaded');
1550
1572
  })();