@mindexec/cli 0.2.458 → 0.2.459

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 +57 -53
  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.b973f5f64y.dll} +0 -0
  20. package/wwwroot/_framework/{MindExecution.Kernel.dh617xfv36.dll → MindExecution.Kernel.k4h9fvi9wb.dll} +0 -0
  21. package/wwwroot/_framework/{MindExecution.Plugins.Admin.0sm50hbae9.dll → MindExecution.Plugins.Admin.u2emae8cb5.dll} +0 -0
  22. package/wwwroot/_framework/{MindExecution.Plugins.Business.3wq01orrbu.dll → MindExecution.Plugins.Business.cysw4qtyke.dll} +0 -0
  23. package/wwwroot/_framework/{MindExecution.Plugins.Concept.z8yl28fa2a.dll → MindExecution.Plugins.Concept.8iehgvzjio.dll} +0 -0
  24. package/wwwroot/_framework/{MindExecution.Plugins.Directory.m7murp5oes.dll → MindExecution.Plugins.Directory.t621fsxq2i.dll} +0 -0
  25. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.yuyrbpf0vh.dll → MindExecution.Plugins.PlanMaster.ft631uc7ki.dll} +0 -0
  26. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.rwxvn00rm2.dll → MindExecution.Plugins.YouTube.arivgu92h1.dll} +0 -0
  27. package/wwwroot/_framework/{MindExecution.Shared.r9k48iyijb.dll → MindExecution.Shared.wm997st9eb.dll} +0 -0
  28. package/wwwroot/_framework/{MindExecution.Web.742aribkxm.dll → MindExecution.Web.osdbdrue4h.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
@@ -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
  })();
@@ -1412,10 +1412,11 @@
1412
1412
  return normalizeImageAssetUrl(url).toLowerCase().includes('/assets/thumbs/');
1413
1413
  }
1414
1414
 
1415
- function isAnimatedGifImageUrl(url) {
1415
+ function isAnimationCapableImageUrl(url) {
1416
1416
  const normalized = normalizeImageAssetUrl(url);
1417
1417
  if (!normalized) return false;
1418
- return normalized.split(/[?#]/)[0].toLowerCase().endsWith('.gif');
1418
+ const pathname = normalized.split(/[?#]/)[0].toLowerCase();
1419
+ return pathname.endsWith('.gif') || pathname.endsWith('.webp');
1419
1420
  }
1420
1421
 
1421
1422
  function getMetadataValueCaseInsensitive(metadata, ...keys) {
@@ -1441,6 +1442,34 @@
1441
1442
  return '';
1442
1443
  }
1443
1444
 
1445
+ function isAnimationCapableImageNodeModel(nodeModel) {
1446
+ if (!nodeModel || getNodeContentTypeLower(nodeModel) !== 'image') {
1447
+ return false;
1448
+ }
1449
+
1450
+ const metadata = getNodeMetadata(nodeModel) || {};
1451
+ const mimeType = getMetadataValueCaseInsensitive(
1452
+ metadata,
1453
+ 'fileMime',
1454
+ 'FileMime',
1455
+ 'mimeType',
1456
+ 'MimeType'
1457
+ ).toLowerCase();
1458
+ if (mimeType === 'image/gif' || mimeType === 'image/webp') {
1459
+ return true;
1460
+ }
1461
+
1462
+ return [
1463
+ nodeModel?.response,
1464
+ nodeModel?.Response,
1465
+ metadata?.OriginalPath,
1466
+ metadata?.OriginalUrl,
1467
+ metadata?.originalUrl,
1468
+ metadata?.ImageUrl,
1469
+ metadata?.imageUrl
1470
+ ].some(isAnimationCapableImageUrl);
1471
+ }
1472
+
1444
1473
  function isAnimatedGifImageNodeModel(nodeModel) {
1445
1474
  if (!nodeModel || getNodeContentTypeLower(nodeModel) !== 'image') {
1446
1475
  return false;
@@ -1466,7 +1495,7 @@
1466
1495
  metadata?.originalUrl,
1467
1496
  metadata?.ImageUrl,
1468
1497
  metadata?.imageUrl
1469
- ].some(isAnimatedGifImageUrl);
1498
+ ].some(url => normalizeImageAssetUrl(url).split(/[?#]/)[0].toLowerCase().endsWith('.gif'));
1470
1499
  }
1471
1500
 
1472
1501
  function getAnimatedGifOriginalReference(nodeModel, fallback = '') {
@@ -1683,9 +1712,9 @@
1683
1712
  const metadata = ensureNodeMetadataObject(nodeModel);
1684
1713
  const normalizedContent = normalizeImageAssetUrl(nextContent);
1685
1714
  const thumbnailUrl = normalizeImageAssetUrl(metadata?.ThumbnailUrl || metadata?.thumbnailUrl || '');
1686
- const isAnimatedGif = isAnimatedGifImageNodeModel(nodeModel);
1715
+ const isAnimatedImage = isAnimationCapableImageNodeModel(nodeModel);
1687
1716
 
1688
- if (isAnimatedGif) {
1717
+ if (isAnimatedImage) {
1689
1718
  const originalReference = getAnimatedGifOriginalReference(nodeModel, normalizedContent);
1690
1719
  if (originalReference) {
1691
1720
  setNodeImageOriginalReference(nodeModel, originalReference);
@@ -1697,7 +1726,7 @@
1697
1726
  setNodeImageOriginalReference(nodeModel, normalizedContent);
1698
1727
  }
1699
1728
 
1700
- if (thumbnailUrl && !isAnimatedGif) {
1729
+ if (thumbnailUrl && !isAnimatedImage) {
1701
1730
  setNodeImagePreviewUrl(nodeModel, thumbnailUrl);
1702
1731
  return normalizeImageAssetUrl(nodeModel.response ?? nodeModel.Response ?? '');
1703
1732
  }
@@ -1824,15 +1853,15 @@
1824
1853
  ''
1825
1854
  );
1826
1855
  const missingAssetUrl = normalizeImageAssetUrl(metadata?.MissingAssetUrl || '');
1827
- const isAnimatedGif = isAnimatedGifImageNodeModel(nodeModel);
1856
+ const isAnimatedImage = isAnimationCapableImageNodeModel(nodeModel);
1828
1857
  const responseFullResUrl = isImageContent && canUseAsFullResImageUrl(responseUrl) ? responseUrl : '';
1829
1858
  const missingFullResUrl = canUseAsFullResImageUrl(missingAssetUrl) ? missingAssetUrl : '';
1830
- const mediaPreviewUrl = isAnimatedGif ? '' : (thumbnailUrl || snapshotUrl || previewImageUrl);
1831
- const responsePreviewUrl = isImageContent && !isAnimatedGif ? responseUrl : '';
1859
+ const mediaPreviewUrl = isAnimatedImage ? '' : (thumbnailUrl || snapshotUrl || previewImageUrl);
1860
+ const responsePreviewUrl = isImageContent && !isAnimatedImage ? responseUrl : '';
1832
1861
  const fullResUrl = isImageContent
1833
1862
  ? (originalUrl || originalPath || responseFullResUrl || missingFullResUrl)
1834
1863
  : (originalUrl || originalPath || snapshotUrl || previewImageUrl || thumbnailUrl || missingFullResUrl);
1835
- const previewUrl = isAnimatedGif
1864
+ const previewUrl = isAnimatedImage
1836
1865
  ? (fullResUrl || responseUrl || originalPath || originalUrl || missingAssetUrl)
1837
1866
  : (mediaPreviewUrl || responsePreviewUrl || originalPath || originalUrl || missingAssetUrl);
1838
1867
 
@@ -1846,13 +1875,14 @@
1846
1875
  missingAssetUrl,
1847
1876
  previewUrl,
1848
1877
  fullResUrl,
1849
- animatedGif: isAnimatedGif
1878
+ animationCapable: isAnimatedImage,
1879
+ animatedGif: isAnimatedImage
1850
1880
  };
1851
1881
  }
1852
1882
 
1853
1883
  function hasNodeImagePreviewSource(assetUrls) {
1854
1884
  if (!assetUrls || typeof assetUrls !== 'object') return false;
1855
- if (assetUrls.animatedGif === true) return false;
1885
+ if (assetUrls.animationCapable === true || assetUrls.animatedGif === true) return false;
1856
1886
  return !!(
1857
1887
  assetUrls.thumbnailUrl ||
1858
1888
  (assetUrls.responseUrl && isThumbnailImageUrl(assetUrls.responseUrl))
@@ -1879,7 +1909,7 @@
1879
1909
  if (!assetUrls.previewUrl) {
1880
1910
  return;
1881
1911
  }
1882
- if (assetUrls.animatedGif === true) {
1912
+ if (assetUrls.animationCapable === true || assetUrls.animatedGif === true) {
1883
1913
  return;
1884
1914
  }
1885
1915
 
@@ -4570,6 +4600,7 @@
4570
4600
  bringNodeToFront: bringNodeToFront,
4571
4601
  updateNodeSelectionStyle: updateNodeSelectionStyle,
4572
4602
  applySingleSelection: applySingleSelection,
4603
+ moveCursorAfterNodePlacement: moveCursorAfterNodePlacement,
4573
4604
  resetState: resetState,
4574
4605
  captureDeleteSnapshot: captureDeleteSnapshot,
4575
4606
  // ▼▼▼ [New] Expose helper function for external callers ▼▼▼
@@ -4580,6 +4611,7 @@
4580
4611
  updateAnimatedImageStatusTextures: updateAnimatedImageStatusTextures,
4581
4612
  applyCanonicalNodeResponse: applyCanonicalNodeResponse,
4582
4613
  isGeneratedImageNodeModel: isGeneratedImageNodeModel,
4614
+ isAnimationCapableImageNodeModel: isAnimationCapableImageNodeModel,
4583
4615
  isAnimatedGifImageNodeModel: isAnimatedGifImageNodeModel,
4584
4616
  getNodeImageAssetUrls: getNodeImageAssetUrls,
4585
4617
  setNodeImageOriginalUrl: setNodeImageOriginalUrl,