@mindexec/cli 0.2.446 → 0.2.447

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 (31) hide show
  1. package/README.md +2 -2
  2. package/package.json +2 -2
  3. package/remote-fast/osx-arm64/mindexec-remote-fast.dll +0 -0
  4. package/remote-fast/osx-x64/mindexec-remote-fast.dll +0 -0
  5. package/remote-fast/win-x64/mindexec-remote-fast.dll +0 -0
  6. package/remote-hub.js +1 -1
  7. package/scripts/remote-fleet-render-smoke.mjs +74 -36
  8. package/scripts/remote-hub-smoke.mjs +7 -0
  9. package/wwwroot/_content/MindExecution.Shared/js/background-themes.js +1 -3
  10. package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +199 -100
  11. package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-hotpath.js +902 -0
  12. package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +56 -385
  13. package/wwwroot/_content/MindExecution.Shared/js/mind-map-interactions.js +90 -134
  14. package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-renderer.js +167 -24
  15. package/wwwroot/_content/MindExecution.Shared/js/mind-map-nodes.js +92 -50
  16. package/wwwroot/_content/MindExecution.Shared/js/mind-map-pipeline.js +35 -12
  17. package/wwwroot/_content/MindExecution.Shared/js/renderers/CSS3DRenderer.js +35 -2
  18. package/wwwroot/_framework/{MindExecution.Core.djyzj5mxyk.dll → MindExecution.Core.mswl4wlkm9.dll} +0 -0
  19. package/wwwroot/_framework/{MindExecution.Kernel.6e5cj9aijz.dll → MindExecution.Kernel.zupeldfptg.dll} +0 -0
  20. package/wwwroot/_framework/{MindExecution.Plugins.Admin.siulh9tfrf.dll → MindExecution.Plugins.Admin.hq0vyqnqtn.dll} +0 -0
  21. package/wwwroot/_framework/{MindExecution.Plugins.Business.8cjtrc7qd3.dll → MindExecution.Plugins.Business.pucw3o7rno.dll} +0 -0
  22. package/wwwroot/_framework/{MindExecution.Plugins.Concept.rpp78mc75u.dll → MindExecution.Plugins.Concept.557tzzkrrx.dll} +0 -0
  23. package/wwwroot/_framework/{MindExecution.Plugins.Directory.wfv0ff9v4u.dll → MindExecution.Plugins.Directory.zh1ddg1fn6.dll} +0 -0
  24. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.jdppnbd6g4.dll → MindExecution.Plugins.PlanMaster.0z9pzynpu9.dll} +0 -0
  25. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.a43f2q3pkw.dll → MindExecution.Plugins.YouTube.y8g77r54fa.dll} +0 -0
  26. package/wwwroot/_framework/{MindExecution.Shared.gwtyj0e9lw.dll → MindExecution.Shared.1ymsdbwid2.dll} +0 -0
  27. package/wwwroot/_framework/{MindExecution.Web.j3zaapcxtt.dll → MindExecution.Web.bb1txupuv0.dll} +0 -0
  28. package/wwwroot/_framework/blazor.boot.json +21 -21
  29. package/wwwroot/index.html +2 -1
  30. package/wwwroot/service-worker-assets.js +35 -31
  31. package/wwwroot/service-worker.js +1 -1
@@ -1649,65 +1649,18 @@ window.MindMapInteractions = (function () {
1649
1649
  return null;
1650
1650
  }
1651
1651
 
1652
- function getNodeProjectedClientFrame(module, entry) {
1653
- if (!module?.camera || !module?.container || !entry) {
1654
- return null;
1655
- }
1656
-
1657
- const rect = module.container.getBoundingClientRect?.();
1658
- if (!rect || rect.width <= 0 || rect.height <= 0) {
1659
- return null;
1660
- }
1661
-
1662
- const sourceObject = entry.glObject || entry.cssObject;
1663
- const position = sourceObject?.position;
1664
- if (!position) {
1665
- return null;
1666
- }
1667
-
1668
- const width = Number(
1669
- entry.glObject?.userData?.worldWidth ||
1670
- entry.cssObject?.userData?.worldWidth ||
1671
- entry.model?.width ||
1672
- entry.model?.Width ||
1673
- 0
1674
- );
1675
- const height = Number(
1676
- entry.glObject?.userData?.worldHeight ||
1677
- entry.cssObject?.userData?.worldHeight ||
1678
- entry.model?.height ||
1679
- entry.model?.Height ||
1680
- 0
1681
- );
1682
- if (!(width > 0 && height > 0)) {
1683
- return null;
1684
- }
1685
-
1686
- const corners = [
1687
- new THREE.Vector3(position.x, position.y, position.z || 0),
1688
- new THREE.Vector3(position.x + width, position.y, position.z || 0),
1689
- new THREE.Vector3(position.x, position.y - height, position.z || 0),
1690
- new THREE.Vector3(position.x + width, position.y - height, position.z || 0)
1691
- ];
1692
-
1693
- const xs = [];
1694
- const ys = [];
1695
- for (const corner of corners) {
1696
- corner.project(module.camera);
1697
- xs.push(rect.left + ((corner.x + 1) * rect.width / 2));
1698
- ys.push(rect.top + ((-corner.y + 1) * rect.height / 2));
1699
- }
1700
-
1701
- return {
1702
- left: Math.min(...xs),
1703
- right: Math.max(...xs),
1704
- top: Math.min(...ys),
1705
- bottom: Math.max(...ys)
1706
- };
1652
+ function getNodeProjectedClientFrame(module, entry, context = null, nodeId = null) {
1653
+ return window.MindMapCss3dHotPath?.getProjectedClientFrame?.(
1654
+ module,
1655
+ entry,
1656
+ context,
1657
+ getCachedViewportMetrics(module),
1658
+ nodeId
1659
+ ) || null;
1707
1660
  }
1708
1661
 
1709
- function isClientPointInsideNodeFrame(module, entry, clientX, clientY, padding = 2) {
1710
- const frame = getNodeProjectedClientFrame(module, entry);
1662
+ function isClientPointInsideNodeFrame(module, entry, clientX, clientY, padding = 2, context = null, nodeId = null) {
1663
+ const frame = getNodeProjectedClientFrame(module, entry, context, nodeId);
1711
1664
  if (!frame) {
1712
1665
  return true;
1713
1666
  }
@@ -1718,13 +1671,13 @@ window.MindMapInteractions = (function () {
1718
1671
  clientY <= frame.bottom + padding;
1719
1672
  }
1720
1673
 
1721
- function isOutOfFrameNodeDomHit(module, nodeId, clientX, clientY) {
1674
+ function isOutOfFrameNodeDomHit(module, nodeId, clientX, clientY, context = null) {
1722
1675
  const entry = nodeId ? module?.nodeObjectsById?.get?.(nodeId) : null;
1723
1676
  if (!entry?.cssObject?.element) {
1724
1677
  return false;
1725
1678
  }
1726
1679
 
1727
- return !isClientPointInsideNodeFrame(module, entry, clientX, clientY, 3);
1680
+ return !isClientPointInsideNodeFrame(module, entry, clientX, clientY, 3, context, nodeId);
1728
1681
  }
1729
1682
 
1730
1683
  function resolveNodeIdFromHitObject(object) {
@@ -1871,13 +1824,15 @@ window.MindMapInteractions = (function () {
1871
1824
  return bestHit;
1872
1825
  }
1873
1826
 
1874
- function findCssNodeHitAtClientPoint(module, clientX, clientY) {
1827
+ function findCssNodeHitAtClientPoint(module, clientX, clientY, context = null) {
1875
1828
  if (!module?.nodeObjectsById) {
1876
1829
  return null;
1877
1830
  }
1878
1831
 
1879
1832
  if (typeof document !== 'undefined' && typeof document.elementsFromPoint === 'function') {
1880
- const domStack = document.elementsFromPoint(clientX, clientY);
1833
+ const domStack = context
1834
+ ? window.MindMapCss3dHotPath.getDomStack(module, context)
1835
+ : document.elementsFromPoint(clientX, clientY);
1881
1836
  let bestHit = null;
1882
1837
  let bestPriority = -Infinity;
1883
1838
 
@@ -1903,7 +1858,7 @@ window.MindMapInteractions = (function () {
1903
1858
  continue;
1904
1859
  }
1905
1860
 
1906
- if (!isClientPointInsideNodeFrame(module, entry, clientX, clientY, 2)) {
1861
+ if (!isClientPointInsideNodeFrame(module, entry, clientX, clientY, 2, context, nodeId)) {
1907
1862
  continue;
1908
1863
  }
1909
1864
 
@@ -1927,8 +1882,13 @@ window.MindMapInteractions = (function () {
1927
1882
 
1928
1883
  let bestHit = null;
1929
1884
  let bestPriority = -Infinity;
1930
- const worldPoint = getWorldPoint(module, clientX, clientY);
1931
- const candidateIds = getCssFallbackCandidateIds(module, worldPoint);
1885
+ const worldPoint = window.MindMapCss3dHotPath
1886
+ ? window.MindMapCss3dHotPath.getContextWorldPoint(
1887
+ context,
1888
+ () => getWorldPoint(module, clientX, clientY)
1889
+ )
1890
+ : getWorldPoint(module, clientX, clientY);
1891
+ const candidateIds = getCssFallbackCandidateIds(module, worldPoint, context);
1932
1892
  for (let i = 0; i < candidateIds.length; i++) {
1933
1893
  const nodeId = candidateIds[i];
1934
1894
  const entry = module.nodeObjectsById.get(nodeId);
@@ -1945,7 +1905,7 @@ window.MindMapInteractions = (function () {
1945
1905
  continue;
1946
1906
  }
1947
1907
 
1948
- if (!isClientPointInsideNodeFrame(module, entry, clientX, clientY, 2)) {
1908
+ if (!isClientPointInsideNodeFrame(module, entry, clientX, clientY, 2, context, nodeId)) {
1949
1909
  continue;
1950
1910
  }
1951
1911
 
@@ -2142,11 +2102,20 @@ window.MindMapInteractions = (function () {
2142
2102
  cellRadius: normalizedCellRadius
2143
2103
  });
2144
2104
  if (Array.isArray(cachedCandidateIds)) {
2105
+ module._lastHitTestCandidateSource = 'worker';
2106
+ window.MindMapCss3dHotPath?.recordHitCandidates?.(module, 'worker', cachedCandidateIds.length);
2145
2107
  return cachedCandidateIds;
2146
2108
  }
2147
2109
 
2148
- if (!module.spatialGrid) return null;
2149
- return collectLocalHitTestCandidateNodeIds(module, point, normalizedCellRadius);
2110
+ if (!module.spatialGrid || module._spatialIndexRepairState) {
2111
+ module._lastHitTestCandidateSource = 'unavailable';
2112
+ window.MindMapCss3dHotPath?.noteUnavailableIndex?.(module, 'pointer-index-unavailable');
2113
+ return null;
2114
+ }
2115
+ const localIds = collectLocalHitTestCandidateNodeIds(module, point, normalizedCellRadius);
2116
+ module._lastHitTestCandidateSource = 'spatial-grid';
2117
+ window.MindMapCss3dHotPath?.recordHitCandidates?.(module, 'spatial-grid', localIds.length);
2118
+ return localIds;
2150
2119
  }
2151
2120
 
2152
2121
  function appendMeshesForHitTestCandidateIds(module, candidateIds, meshes, options = {}) {
@@ -2207,49 +2176,19 @@ window.MindMapInteractions = (function () {
2207
2176
  };
2208
2177
  }
2209
2178
 
2210
- function collectLocalSelectionCandidateNodeIds(module, selectionRect) {
2211
- if (!module?.spatialGrid || !selectionRect) {
2212
- return [];
2213
- }
2214
-
2215
- const cellSize = Math.max(1, Number(module.GRID_CELL_SIZE || 500));
2216
- const startCellX = Math.floor(Number(selectionRect.left || 0) / cellSize);
2217
- const endCellX = Math.floor(Number(selectionRect.right || 0) / cellSize);
2218
- const startCellY = Math.floor(Number(selectionRect.bottom || 0) / cellSize);
2219
- const endCellY = Math.floor(Number(selectionRect.top || 0) / cellSize);
2220
- const candidateIds = [];
2221
- const seenIds = new Set();
2222
-
2223
- for (let cellX = startCellX; cellX <= endCellX; cellX++) {
2224
- for (let cellY = startCellY; cellY <= endCellY; cellY++) {
2225
- const nodeIds = module.spatialGrid.get(`${cellX}:${cellY}`);
2226
- if (!nodeIds) {
2227
- continue;
2228
- }
2229
-
2230
- nodeIds.forEach((nodeId) => {
2231
- const normalizedNodeId = String(nodeId || '').trim();
2232
- if (!normalizedNodeId || seenIds.has(normalizedNodeId)) {
2233
- return;
2234
- }
2235
-
2236
- seenIds.add(normalizedNodeId);
2237
- candidateIds.push(normalizedNodeId);
2238
- });
2239
- }
2240
- }
2241
-
2242
- return candidateIds;
2243
- }
2244
-
2245
2179
  function collectSelectionBoxNodeIdsLocal(module, selectionRect) {
2246
2180
  const selectedIds = new Set();
2247
2181
  if (!module?.nodeObjectsById || !selectionRect) {
2248
2182
  return selectedIds;
2249
2183
  }
2250
2184
 
2251
- const candidateIds = collectLocalSelectionCandidateNodeIds(module, selectionRect);
2185
+ let candidateIds = window.MindMapCss3dHotPath?.collectSpatialRectCandidateIds?.(module, selectionRect) ?? null;
2186
+ if (candidateIds === null) {
2187
+ window.MindMapCss3dHotPath?.noteUnavailableIndex?.(module, 'selection-box-index-unavailable');
2188
+ candidateIds = window.MindMapCss3dHotPath?.getBoundedFallbackIds?.(module) || [];
2189
+ }
2252
2190
  const processCandidateNode = (nodeId) => {
2191
+ window.MindMapCss3dHotPath?.increment?.(module, 'selection', 'nodesVisited');
2253
2192
  const nodeEntry = module.nodeObjectsById.get(nodeId);
2254
2193
  if (!nodeEntry?.glObject?.userData) {
2255
2194
  return;
@@ -2271,12 +2210,8 @@ window.MindMapInteractions = (function () {
2271
2210
  }
2272
2211
  };
2273
2212
 
2274
- if (candidateIds.length > 0) {
2275
- for (let i = 0; i < candidateIds.length; i++) {
2276
- processCandidateNode(candidateIds[i]);
2277
- }
2278
- } else {
2279
- module.nodeObjectsById.forEach((_, nodeId) => processCandidateNode(nodeId));
2213
+ for (let i = 0; i < candidateIds.length; i++) {
2214
+ processCandidateNode(candidateIds[i]);
2280
2215
  }
2281
2216
 
2282
2217
  return selectedIds;
@@ -2331,27 +2266,28 @@ window.MindMapInteractions = (function () {
2331
2266
  });
2332
2267
  }
2333
2268
 
2334
- function getCssFallbackCandidateIds(module, worldPoint) {
2269
+ function getCssFallbackCandidateIds(module, worldPoint, context = null) {
2335
2270
  if (!module?.nodeObjectsById) {
2336
2271
  return [];
2337
2272
  }
2338
2273
 
2339
- const candidateIds = worldPoint
2340
- ? getHitTestCandidateNodeIds(module, worldPoint, { visibleOnly: false })
2341
- : [];
2274
+ if (context?.candidateIds !== undefined) return context.candidateIds;
2275
+ const candidateIds = worldPoint ? getHitTestCandidateNodeIds(module, worldPoint, { visibleOnly: false }) : [];
2342
2276
  if (Array.isArray(candidateIds)) {
2277
+ if (context) {
2278
+ context.candidateIds = candidateIds;
2279
+ context.candidateSource = module._lastHitTestCandidateSource || 'spatial-grid';
2280
+ }
2343
2281
  return candidateIds;
2344
2282
  }
2345
2283
 
2346
- const fallbackIds = new Set();
2347
- const addId = value => { const id = String(value || '').trim(); if (id && module.nodeObjectsById.has(id)) fallbackIds.add(id); };
2348
- module._visibleIds?.forEach?.(addId);
2349
- module._prevVisibleIds?.forEach?.(addId);
2350
- addId(module.selectedNodeIdJs);
2351
- module.multiSelectedNodeIds?.forEach?.(addId);
2352
- addId(module.draggedNodeId);
2353
- module.container?.querySelectorAll?.('[data-node-id]')?.forEach?.(element => addId(element.dataset?.nodeId));
2354
- return Array.from(fallbackIds);
2284
+ const fallbackIds = window.MindMapCss3dHotPath?.getBoundedFallbackIds?.(module) || [];
2285
+ window.MindMapCss3dHotPath?.recordHitCandidates?.(module, 'bounded-fallback', fallbackIds.length);
2286
+ if (context) {
2287
+ context.candidateIds = fallbackIds;
2288
+ context.candidateSource = 'bounded-fallback';
2289
+ }
2290
+ return fallbackIds;
2355
2291
  }
2356
2292
  // ▲▲▲ [New] ▲▲▲
2357
2293
 
@@ -2964,7 +2900,8 @@ window.MindMapInteractions = (function () {
2964
2900
  }
2965
2901
 
2966
2902
  const nodeId = getTargetNodeId(target);
2967
- if (nodeId && event && isOutOfFrameNodeDomHit(module, nodeId, event.clientX, event.clientY)) {
2903
+ if (nodeId && event && isOutOfFrameNodeDomHit(
2904
+ module, nodeId, event.clientX, event.clientY, getPointerHitContext(module, event))) {
2968
2905
  return false;
2969
2906
  }
2970
2907
 
@@ -2977,23 +2914,40 @@ window.MindMapInteractions = (function () {
2977
2914
  const topCssHit = event ? getPointerCssHit(module, event) : null;
2978
2915
  return topCssHit?.nodeId || nodeId;
2979
2916
  }
2980
-
2981
- const topCssHit = getPointerCssHit(module, event);
2982
- if (topCssHit?.nodeId && topCssHit.nodeId !== nodeId) {
2983
- return topCssHit.nodeId;
2917
+ const context = getPointerHitContext(module, event);
2918
+ if (nodeId && !isOutOfFrameNodeDomHit(module, nodeId, event.clientX, event.clientY, context)) {
2919
+ const entry = module.nodeObjectsById?.get?.(nodeId) || null;
2920
+ if (entry) {
2921
+ context.cssHit = {
2922
+ nodeId,
2923
+ entry,
2924
+ priority: getEntryInteractionPriority(module, entry, nodeId)
2925
+ };
2926
+ }
2927
+ return nodeId;
2984
2928
  }
2985
-
2986
- return isOutOfFrameNodeDomHit(module, nodeId, event.clientX, event.clientY)
2987
- ? null
2988
- : nodeId;
2929
+ const topCssHit = getPointerCssHit(module, event);
2930
+ return topCssHit?.nodeId || null;
2989
2931
  }
2990
- function getPointerCssHit(module, event) {
2932
+ function getPointerHitContext(module, event) {
2991
2933
  if (!event || !module) return null;
2992
2934
  let context = pointerHitContexts.get(event);
2993
2935
  if (!context || context.module !== module) {
2994
- context = { module, cssHit: findCssNodeHitAtClientPoint(module, event.clientX, event.clientY) };
2936
+ context = window.MindMapCss3dHotPath.createPointerHitContext(
2937
+ module,
2938
+ event,
2939
+ getCachedViewportMetrics(module)
2940
+ );
2995
2941
  pointerHitContexts.set(event, context);
2996
2942
  }
2943
+ return context;
2944
+ }
2945
+ function getPointerCssHit(module, event) {
2946
+ const context = getPointerHitContext(module, event);
2947
+ if (!context) return null;
2948
+ if (context.cssHit === undefined) {
2949
+ context.cssHit = findCssNodeHitAtClientPoint(module, event.clientX, event.clientY, context);
2950
+ }
2997
2951
  return context.cssHit;
2998
2952
  }
2999
2953
  function getMountedEmbedCanvasDragTarget(module, target, event) {
@@ -3025,7 +2979,8 @@ window.MindMapInteractions = (function () {
3025
2979
  return null;
3026
2980
  }
3027
2981
 
3028
- if (event && !isClientPointInsideNodeFrame(module, nodeEntry, event.clientX, event.clientY, 3)) {
2982
+ if (event && !isClientPointInsideNodeFrame(
2983
+ module, nodeEntry, event.clientX, event.clientY, 3, getPointerHitContext(module, event), nodeId)) {
3029
2984
  return null;
3030
2985
  }
3031
2986
 
@@ -3387,6 +3342,7 @@ window.MindMapInteractions = (function () {
3387
3342
  if (previousIds.size === selectedIds.size && [...selectedIds].every(id => previousIds.has(id))) return;
3388
3343
 
3389
3344
  const changedIds = new Set([...previousIds, ...selectedIds]);
3345
+ window.MindMapCss3dHotPath?.increment?.(module, 'selection', 'nodesVisited', changedIds.size);
3390
3346
  changedIds.forEach(nodeId => {
3391
3347
  const entry = module.nodeObjectsById.get(nodeId);
3392
3348
  if (String(entry?.model?.contentType ?? entry?.model?.ContentType ?? '').toLowerCase() !== 'embed' || !entry?.cssObject?.element) return;
@@ -51,7 +51,7 @@
51
51
  // ▲▲▲ [Usage] ▲▲▲
52
52
 
53
53
  // Procedural line rendering settings
54
- const LOD_RENDERER_BUILD_ID = '20260713-normal-boundary-delta-v844';
54
+ const LOD_RENDERER_BUILD_ID = '20260725-cold-mid-line-restore-v846';
55
55
  const DirtyKind = Object.freeze({
56
56
  ResidentFullRebuild: 'resident-full-rebuild',
57
57
  ResidentPatch: 'resident-patch',
@@ -585,7 +585,14 @@
585
585
  return !isAnimatedGifImageModel(model);
586
586
  }
587
587
 
588
- if (contentType !== 'embed' && contentType !== 'video') {
588
+ // Every video needs a resident MID/FAR visual. Videos without an
589
+ // explicit preview use a lightweight atlas placeholder so the live
590
+ // <video> element can stay suspended without making the node vanish.
591
+ if (contentType === 'video') {
592
+ return true;
593
+ }
594
+
595
+ if (contentType !== 'embed') {
589
596
  return false;
590
597
  }
591
598
 
@@ -1255,6 +1262,33 @@
1255
1262
  return [invAtlas, invAtlas, invAtlas, invAtlas];
1256
1263
  }
1257
1264
 
1265
+ _getOrCreateImageAtlasSlot(nodeId) {
1266
+ const normalizedId = String(nodeId || '').trim();
1267
+ if (!normalizedId) {
1268
+ return null;
1269
+ }
1270
+
1271
+ let slot = this.imageAtlasSlots.get(normalizedId) || null;
1272
+ if (slot) {
1273
+ return slot;
1274
+ }
1275
+
1276
+ const slotIndex = this._allocateImageAtlasSlot();
1277
+ if (slotIndex < 0) {
1278
+ return null;
1279
+ }
1280
+
1281
+ slot = {
1282
+ slotIndex,
1283
+ key: '',
1284
+ uvRect: this._getImageAtlasUvRect(slotIndex),
1285
+ ready: false,
1286
+ unsupported: false
1287
+ };
1288
+ this.imageAtlasSlots.set(normalizedId, slot);
1289
+ return slot;
1290
+ }
1291
+
1258
1292
  _compactImageAtlasLoadQueue(force = false) {
1259
1293
  const cursor = this._imageAtlasLoadCursor || 0;
1260
1294
  if (cursor <= 0) return;
@@ -1653,35 +1687,114 @@
1653
1687
  }
1654
1688
  }
1655
1689
 
1690
+ _drawVideoLodPlaceholder(slot, nodeWidth, nodeHeight) {
1691
+ if (!slot || !this.imageAtlasCanvas || typeof document === 'undefined') {
1692
+ return false;
1693
+ }
1694
+
1695
+ const ctx = this.imageAtlasCanvas.getContext('2d', { alpha: false });
1696
+ if (!ctx) {
1697
+ return false;
1698
+ }
1699
+
1700
+ const columns = this.imageAtlasColumns || IMAGE_LOD_ATLAS_COLUMNS;
1701
+ const col = slot.slotIndex % columns;
1702
+ const row = Math.floor(slot.slotIndex / columns);
1703
+ const x = col * IMAGE_LOD_THUMBNAIL_SIZE;
1704
+ const y = row * IMAGE_LOD_THUMBNAIL_SIZE;
1705
+ const size = IMAGE_LOD_THUMBNAIL_SIZE;
1706
+ const safeWidth = Math.max(1, Number(nodeWidth || 300));
1707
+ const safeHeight = Math.max(1, Number(nodeHeight || 300));
1708
+ const targetAspect = safeWidth / safeHeight;
1709
+
1710
+ try {
1711
+ ctx.save();
1712
+
1713
+ const background = ctx.createLinearGradient(x, y, x, y + size);
1714
+ background.addColorStop(0, '#26364d');
1715
+ background.addColorStop(1, '#0f172a');
1716
+ ctx.fillStyle = background;
1717
+ ctx.fillRect(x, y, size, size);
1718
+
1719
+ const inset = 4;
1720
+ ctx.strokeStyle = 'rgba(148, 163, 184, 0.58)';
1721
+ ctx.lineWidth = 2;
1722
+ ctx.strokeRect(x + inset, y + inset, size - inset * 2, size - inset * 2);
1723
+
1724
+ // Pre-compensate the icon for the square atlas tile being
1725
+ // stretched to the video's world-space aspect ratio.
1726
+ const radiusX = size * 0.19;
1727
+ const radiusY = Math.min(size * 0.34, radiusX * Math.max(0.55, targetAspect));
1728
+ const centerX = x + size * 0.5;
1729
+ const centerY = y + size * 0.48;
1730
+ ctx.beginPath();
1731
+ ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, Math.PI * 2);
1732
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.18)';
1733
+ ctx.fill();
1734
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.62)';
1735
+ ctx.lineWidth = 2;
1736
+ ctx.stroke();
1737
+
1738
+ const triangleWidth = size * 0.13;
1739
+ const triangleHeight = Math.min(size * 0.24, triangleWidth * Math.max(0.85, targetAspect));
1740
+ ctx.beginPath();
1741
+ ctx.moveTo(centerX - triangleWidth * 0.35, centerY - triangleHeight * 0.5);
1742
+ ctx.lineTo(centerX + triangleWidth * 0.7, centerY);
1743
+ ctx.lineTo(centerX - triangleWidth * 0.35, centerY + triangleHeight * 0.5);
1744
+ ctx.closePath();
1745
+ ctx.fillStyle = '#f8fafc';
1746
+ ctx.fill();
1747
+
1748
+ ctx.fillStyle = 'rgba(226, 232, 240, 0.35)';
1749
+ ctx.fillRect(x + size * 0.18, y + size * 0.82, size * 0.64, 2);
1750
+ ctx.fillStyle = 'rgba(248, 250, 252, 0.78)';
1751
+ ctx.fillRect(x + size * 0.18, y + size * 0.82, size * 0.2, 2);
1752
+ ctx.restore();
1753
+
1754
+ if (this.imageAtlasTexture) {
1755
+ this.imageAtlasTexture.needsUpdate = true;
1756
+ }
1757
+ return true;
1758
+ } catch {
1759
+ try { ctx.restore(); } catch { }
1760
+ return false;
1761
+ }
1762
+ }
1763
+
1656
1764
  _ensureImageLodThumbnail(entry) {
1657
1765
  const nodeId = entry?.model?.id;
1658
1766
  if (!nodeId) {
1659
1767
  return null;
1660
1768
  }
1661
1769
 
1770
+ const contentType = getNodeContentType(entry?.model);
1771
+ const nodeWidth = Math.max(1, Number(entry?.model?.width || entry?.model?.Width || 300));
1772
+ const nodeHeight = Math.max(1, Number(entry?.model?.height || entry?.model?.Height || 300));
1662
1773
  const source = this._getImageLodSource(entry);
1663
1774
  if (!source) {
1775
+ if (contentType === 'video') {
1776
+ const placeholderKey = `video-placeholder|node:${Math.round(nodeWidth)}x${Math.round(nodeHeight)}`;
1777
+ const placeholderSlot = this._getOrCreateImageAtlasSlot(nodeId);
1778
+ if (!placeholderSlot) {
1779
+ return null;
1780
+ }
1781
+ if (placeholderSlot.ready && placeholderSlot.key === placeholderKey) {
1782
+ return placeholderSlot;
1783
+ }
1784
+ if (this._drawVideoLodPlaceholder(placeholderSlot, nodeWidth, nodeHeight)) {
1785
+ placeholderSlot.key = placeholderKey;
1786
+ placeholderSlot.ready = true;
1787
+ placeholderSlot.unsupported = false;
1788
+ return placeholderSlot;
1789
+ }
1790
+ }
1664
1791
  return null;
1665
1792
  }
1666
1793
 
1667
- const nodeWidth = Math.max(1, Number(entry?.model?.width || entry?.model?.Width || 300));
1668
- const nodeHeight = Math.max(1, Number(entry?.model?.height || entry?.model?.Height || 300));
1669
1794
  const sourceKey = `${source.key}|node:${Math.round(nodeWidth)}x${Math.round(nodeHeight)}`;
1670
-
1671
- let slot = this.imageAtlasSlots.get(nodeId) || null;
1795
+ const slot = this._getOrCreateImageAtlasSlot(nodeId);
1672
1796
  if (!slot) {
1673
- const slotIndex = this._allocateImageAtlasSlot();
1674
- if (slotIndex < 0) {
1675
- return null;
1676
- }
1677
- slot = {
1678
- slotIndex,
1679
- key: '',
1680
- uvRect: this._getImageAtlasUvRect(slotIndex),
1681
- ready: false,
1682
- unsupported: false
1683
- };
1684
- this.imageAtlasSlots.set(nodeId, slot);
1797
+ return null;
1685
1798
  }
1686
1799
 
1687
1800
  if (slot.ready && slot.key === sourceKey) {
@@ -1699,6 +1812,14 @@
1699
1812
  return slot;
1700
1813
  }
1701
1814
 
1815
+ if (contentType === 'video' &&
1816
+ this._drawVideoLodPlaceholder(slot, nodeWidth, nodeHeight)) {
1817
+ slot.key = `video-placeholder|source:${sourceKey}`;
1818
+ slot.ready = true;
1819
+ slot.unsupported = false;
1820
+ return slot;
1821
+ }
1822
+
1702
1823
  if (source.fetchFallbackOnDrawFailure === true) {
1703
1824
  slot.key = '';
1704
1825
  slot.ready = false;
@@ -3340,6 +3461,8 @@
3340
3461
  _hasPendingLineDetailRestore(lodBand = this._currentLodBand) {
3341
3462
  return ENABLE_INSTANCED_TEXT_LINES === true &&
3342
3463
  this._lineDetailsPatchPending === true &&
3464
+ this._deferLineRestorePending !== true &&
3465
+ this._disableLineRender !== true &&
3343
3466
  this.isInLODMode === true &&
3344
3467
  lodBand === 'MID' &&
3345
3468
  this.isLoading !== true;
@@ -6642,15 +6765,29 @@
6642
6765
 
6643
6766
  if (disableLines) {
6644
6767
  const cachedLineData = this.lineDataCache.get(nodeId) || null;
6645
- if (cachedLineData) {
6646
- lineCounts.push(cachedLineData.lineCount || 0);
6647
- lineHeights.push(cachedLineData.lineHeightPx || LINE_HEIGHT_PX);
6768
+ const deferredLineData = cachedLineData || (
6769
+ this._deferLineRestorePending === true
6770
+ ? window.MindMapRenderPlan?.resolveResidentLineData?.(
6771
+ this,
6772
+ entry,
6773
+ w,
6774
+ h,
6775
+ false,
6776
+ MAX_LINE_SAMPLES,
6777
+ LINE_HEIGHT_PX,
6778
+ LINE_PADDING_PX
6779
+ ) || null
6780
+ : null
6781
+ );
6782
+ if (deferredLineData) {
6783
+ lineCounts.push(deferredLineData.lineCount || 0);
6784
+ lineHeights.push(deferredLineData.lineHeightPx || LINE_HEIGHT_PX);
6648
6785
  linePaddings.push(
6649
- cachedLineData.paddingX || LINE_PADDING_PX,
6650
- cachedLineData.paddingY || LINE_PADDING_PX
6786
+ deferredLineData.paddingX || LINE_PADDING_PX,
6787
+ deferredLineData.paddingY || LINE_PADDING_PX
6651
6788
  );
6652
6789
  for (let i = 0; i < MAX_LINE_SAMPLES; i++) {
6653
- lineLengths.push(cachedLineData.lineLengths?.[i] || 0);
6790
+ lineLengths.push(deferredLineData.lineLengths?.[i] || 0);
6654
6791
  }
6655
6792
  } else {
6656
6793
  lineCounts.push(0);
@@ -7910,6 +8047,12 @@
7910
8047
  if (!hasResidentInstances) {
7911
8048
  this._setInstancesDirty('initial-board-build');
7912
8049
  }
8050
+ if (shouldDeferLineRestore) {
8051
+ this._scheduleResidentLodWake(
8052
+ 'post-load-line-restore',
8053
+ POST_LOAD_LINE_RESTORE_DELAY_MS + 16
8054
+ );
8055
+ }
7913
8056
  // ▼▼▼ [UX] Show nodes immediately after loading ends ▼▼▼
7914
8057
  if (this.instancedHaloMesh) this.instancedHaloMesh.visible = this.isInLODMode === true && shouldShowInstancedHaloInLodBand(this._currentLodBand);
7915
8058
  if (this.instancedMesh) this.instancedMesh.visible = this.isInLODMode === true;