@mindexec/cli 0.2.219 → 0.2.221

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 (22) hide show
  1. package/package.json +1 -1
  2. package/scripts/remote-fleet-render-smoke.mjs +11 -0
  3. package/wwwroot/_content/MindExecution.Shared/js/background-themes.js +28 -113
  4. package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +1952 -152
  5. package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +375 -98
  6. package/wwwroot/_content/MindExecution.Shared/js/mind-map-interactions.js +108 -49
  7. package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-renderer.js +85 -3
  8. package/wwwroot/_content/MindExecution.Shared/js/mind-map-nodes.js +83 -14
  9. package/wwwroot/_content/MindExecution.Shared/js/mind-map-text-overlay-v2.js +5 -0
  10. package/wwwroot/_content/MindExecution.Shared/js/renderers/CSS3DRenderer.js +2 -2
  11. package/wwwroot/_framework/{MindExecution.Core.c9wgwsw9z6.dll → MindExecution.Core.xsad4wu36e.dll} +0 -0
  12. package/wwwroot/_framework/{MindExecution.Plugins.Admin.29mytzdaun.dll → MindExecution.Plugins.Admin.m5d94977um.dll} +0 -0
  13. package/wwwroot/_framework/{MindExecution.Plugins.Business.g8txk1zzic.dll → MindExecution.Plugins.Business.ntqdn1hta3.dll} +0 -0
  14. package/wwwroot/_framework/{MindExecution.Plugins.Concept.u9kzsgf64o.dll → MindExecution.Plugins.Concept.mqiyx4db4r.dll} +0 -0
  15. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.kibqg6rvqh.dll → MindExecution.Plugins.PlanMaster.fnortumnuu.dll} +0 -0
  16. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.089r64n2hv.dll → MindExecution.Plugins.YouTube.86osu6wpgh.dll} +0 -0
  17. package/wwwroot/_framework/{MindExecution.Shared.tbutfszqeu.dll → MindExecution.Shared.wg2otcw4bv.dll} +0 -0
  18. package/wwwroot/_framework/{MindExecution.Web.7axuoygrwi.dll → MindExecution.Web.gfio3kfxrd.dll} +0 -0
  19. package/wwwroot/_framework/blazor.boot.json +17 -17
  20. package/wwwroot/index.html +3 -3
  21. package/wwwroot/service-worker-assets.js +27 -27
  22. package/wwwroot/service-worker.js +1 -1
@@ -5,7 +5,7 @@
5
5
  const DEBUG = false;
6
6
  const FPS_DEBUG = false;
7
7
  const FRAME_PERF_DEBUG = false;
8
- const MINDMAP_CORE_BUILD_ID = '20260619-ua-immediate-camera-v629';
8
+ const MINDMAP_CORE_BUILD_ID = '20260619-motion-hidden-work-v668';
9
9
  const CanvasPhase = Object.freeze({
10
10
  Booting: 'booting',
11
11
  BoardFileLoading: 'board-file-loading',
@@ -94,7 +94,19 @@ const MINDMAP_CORE_BUILD_ID = '20260619-ua-immediate-camera-v629';
94
94
  const VISIBILITY_CULLING_ZOOM_HEAVY_FRAME_INTERVAL = 10;
95
95
  const FULL_RES_VISIBILITY_CULLING_PAN_FRAME_INTERVAL = 10;
96
96
  const FULL_RES_VISIBILITY_EXIT_MARGIN_SCALE = 1.8;
97
- const CAMERA_MOTION_WEBGL_FRAME_INTERVAL = 10;
97
+ const CAMERA_MOTION_WEBGL_FRAME_INTERVAL = 1;
98
+ const CAMERA_MOTION_GRID_HIDE_DELAY_MS = 90;
99
+ const CAMERA_MOTION_GRID_MIN_STEP_PX = 3;
100
+ const CAMERA_MOTION_GRID_MAX_STEP_PX = 48;
101
+ const CAMERA_MOTION_GRID_OVERSCAN_PX = 512;
102
+ const CAMERA_MOTION_GRID_Z_BUCKET_WORLD = 80;
103
+ const CAMERA_MOTION_GRID_FALLBACK_ENABLED = false;
104
+ const CAMERA_MOTION_PARTIAL_FRAME_SKIP_ENABLED = false;
105
+ const CAMERA_MOTION_VISUAL_ONLY_FAST_PATH_ENABLED = true;
106
+ const CAMERA_MOTION_WEBGL_CANVAS_TRANSFORM_ENABLED = true;
107
+ const CAMERA_STATIONARY_INTERACTION_FRAME_DELAY_MS = 48;
108
+ const CAMERA_MOTION_GLOBAL_STYLE_CLASS_ENABLED = false;
109
+ const CAMERA_DENSE_GLOBAL_STYLE_CLASS_ENABLED = false;
98
110
  const PIPELINE_PROFILE_SECTION_KEYS = Object.freeze([
99
111
  'pipelineViewport',
100
112
  'pipelineScheduling',
@@ -306,8 +318,9 @@ const MINDMAP_CORE_BUILD_ID = '20260619-ua-immediate-camera-v629';
306
318
  return false;
307
319
  }
308
320
 
309
- const width = Math.max(1, Number(module.container.clientWidth || 0));
310
- const height = Math.max(1, Number(module.container.clientHeight || 0));
321
+ const viewportMetrics = getCachedViewportMetrics(module);
322
+ const width = Math.max(1, Number(viewportMetrics.width || 0));
323
+ const height = Math.max(1, Number(viewportMetrics.height || 0));
311
324
  const camera = module.camera;
312
325
  const cursor = module.cursorPosition;
313
326
  const vfov = (camera.fov * Math.PI) / 180;
@@ -376,8 +389,27 @@ const MINDMAP_CORE_BUILD_ID = '20260619-ua-immediate-camera-v629';
376
389
  tintVideoLayoutInNear: false,
377
390
  disableNearVideoGlow: false
378
391
  };
392
+ const MINDCANVAS_PERF_CLASSES_ENABLED =
393
+ CAMERA_MOTION_GLOBAL_STYLE_CLASS_ENABLED === true ||
394
+ CAMERA_DENSE_GLOBAL_STYLE_CLASS_ENABLED === true;
395
+
396
+ function shouldSyncMindCanvasPerfClasses() {
397
+ return MINDCANVAS_PERF_CLASSES_ENABLED === true ||
398
+ !!(_mindCanvasPerfClassState.target &&
399
+ (_mindCanvasPerfClassState.moving || _mindCanvasPerfClassState.dense));
400
+ }
379
401
 
380
402
  function syncMindCanvasPerfClasses({ moving = false, dense = false } = {}) {
403
+ if (MINDCANVAS_PERF_CLASSES_ENABLED !== true) {
404
+ if (_mindCanvasPerfClassState.target &&
405
+ (_mindCanvasPerfClassState.moving || _mindCanvasPerfClassState.dense)) {
406
+ _mindCanvasPerfClassState.target.classList?.remove?.('mindcanvas-is-moving', 'mindcanvas-is-dense');
407
+ }
408
+ _mindCanvasPerfClassState.moving = false;
409
+ _mindCanvasPerfClassState.dense = false;
410
+ return;
411
+ }
412
+
381
413
  if (typeof document === 'undefined' || !document.body) {
382
414
  return;
383
415
  }
@@ -390,15 +422,51 @@ const MINDMAP_CORE_BUILD_ID = '20260619-ua-immediate-camera-v629';
390
422
  }
391
423
  _mindCanvasPerfClassState.target = targetElement;
392
424
 
393
- if (_mindCanvasPerfClassState.moving !== moving) {
394
- targetElement.classList.toggle('mindcanvas-is-moving', moving);
395
- _mindCanvasPerfClassState.moving = moving;
425
+ const normalizedMoving = CAMERA_MOTION_GLOBAL_STYLE_CLASS_ENABLED === true && moving === true;
426
+ const normalizedDense = CAMERA_DENSE_GLOBAL_STYLE_CLASS_ENABLED === true && dense === true;
427
+
428
+ if (_mindCanvasPerfClassState.moving !== normalizedMoving) {
429
+ targetElement.classList.toggle('mindcanvas-is-moving', normalizedMoving);
430
+ _mindCanvasPerfClassState.moving = normalizedMoving;
431
+ }
432
+
433
+ if (_mindCanvasPerfClassState.dense !== normalizedDense) {
434
+ targetElement.classList.toggle('mindcanvas-is-dense', normalizedDense);
435
+ _mindCanvasPerfClassState.dense = normalizedDense;
436
+ }
437
+ }
438
+
439
+ function isContinuousBackgroundTheme(module) {
440
+ if (!module?.currentThemeObject || typeof window === 'undefined') {
441
+ return false;
442
+ }
443
+
444
+ const mode = window.MindMapThemes?.getAnimationMode?.(module.currentThemeName, module.currentThemeObject);
445
+ return mode === 'continuous';
446
+ }
447
+
448
+ function canUseWebglCanvasTransformForCameraMotion(module) {
449
+ if (CAMERA_MOTION_WEBGL_CANVAS_TRANSFORM_ENABLED !== true) {
450
+ return false;
451
+ }
452
+
453
+ // Static backgrounds can move as a single viewport surface. Continuous
454
+ // themes need real animation frames during interaction; freezing the
455
+ // last WebGL canvas makes particle themes look like they are skipping.
456
+ if (isContinuousBackgroundTheme(module) === true) {
457
+ return false;
396
458
  }
397
459
 
398
- if (_mindCanvasPerfClassState.dense !== dense) {
399
- targetElement.classList.toggle('mindcanvas-is-dense', dense);
400
- _mindCanvasPerfClassState.dense = dense;
460
+ return true;
461
+ }
462
+
463
+ function deferContinuousThemeForCameraMotion(module) {
464
+ if (!isContinuousBackgroundTheme(module)) {
465
+ return false;
401
466
  }
467
+
468
+ module._themeCameraMotionDeferred = true;
469
+ return true;
402
470
  }
403
471
 
404
472
  function syncRenderDebugBodyClasses(flags = {}) {
@@ -455,7 +523,8 @@ const MINDMAP_CORE_BUILD_ID = '20260619-ua-immediate-camera-v629';
455
523
  freezeVisibilityDuringMotion: false,
456
524
  freezeLodDuringMotion: false,
457
525
  enableFramePerfProbe: false,
458
- enableCss3dWillChange: true,
526
+ sampleWebglDuringCameraMotion: false,
527
+ enableCss3dWillChange: false,
459
528
  enableThemeAnimation: true,
460
529
  enableMenuOverlayUi: true,
461
530
  enableMultiSelectOverlayUi: true,
@@ -684,6 +753,15 @@ const MINDMAP_CORE_BUILD_ID = '20260619-ua-immediate-camera-v629';
684
753
  };
685
754
  }
686
755
 
756
+ const cachedContainerWidth = Math.max(0, Number(module?._lastContainerWidth || 0));
757
+ const cachedContainerHeight = Math.max(0, Number(module?._lastContainerHeight || 0));
758
+ if (cachedContainerWidth > 0 && cachedContainerHeight > 0) {
759
+ return {
760
+ width: cachedContainerWidth,
761
+ height: cachedContainerHeight
762
+ };
763
+ }
764
+
687
765
  return {
688
766
  width: Math.max(1, Number(module?.container?.clientWidth || 0)),
689
767
  height: Math.max(1, Number(module?.container?.clientHeight || 0))
@@ -851,10 +929,170 @@ const MINDMAP_CORE_BUILD_ID = '20260619-ua-immediate-camera-v629';
851
929
  };
852
930
  }
853
931
 
932
+ function createEmptyFramePathStats(startedAt = 0) {
933
+ return {
934
+ total: 0,
935
+ visualOnly: 0,
936
+ stationaryVisual: 0,
937
+ residentVisual: 0,
938
+ earlyCameraOnly: 0,
939
+ css3dCameraFast: 0,
940
+ normal: 0,
941
+ visibilityCulling: 0,
942
+ lodUpdateCalled: 0,
943
+ css3dTransformSync: 0,
944
+ css3dRender: 0,
945
+ textOverlaySync: 0,
946
+ hiddenWork: 0,
947
+ visualOnlyPercent: 0,
948
+ cameraFastPercent: 0,
949
+ normalPercent: 0,
950
+ hiddenWorkPercent: 0,
951
+ lastMode: '',
952
+ lastReason: '',
953
+ lastViewSyncMode: '',
954
+ lastLodSkipReason: '',
955
+ lastVisibilitySkipReason: '',
956
+ startedAt: Number(startedAt || 0),
957
+ updatedAt: 0
958
+ };
959
+ }
960
+
961
+ function getFrameProfilePathMode(frameProfile) {
962
+ if (!frameProfile) {
963
+ return 'none';
964
+ }
965
+
966
+ if (frameProfile.uaUltraResidentCameraOnly === true) {
967
+ return 'ultra-resident-camera-only';
968
+ }
969
+
970
+ if (frameProfile.uaUltraEarlyCameraOnly === true) {
971
+ return 'ultra-camera-only';
972
+ }
973
+
974
+ if (frameProfile.uaVisualOnlyMotion === true) {
975
+ return 'visual-only';
976
+ }
977
+
978
+ if (frameProfile.uaStationaryVisualMotion === true) {
979
+ return 'stationary-visual';
980
+ }
981
+
982
+ if (frameProfile.uaResidentVisualOnlyMotion === true) {
983
+ return 'resident-visual';
984
+ }
985
+
986
+ const viewSyncMode = String(frameProfile.viewSyncMode || '');
987
+ if (viewSyncMode === 'ua-early-camera-only') {
988
+ return 'early-camera-only';
989
+ }
990
+
991
+ if (viewSyncMode === 'ua-stationary-camera-navigation') {
992
+ return 'stationary-camera-navigation';
993
+ }
994
+
995
+ if (frameProfile.usedCss3dCameraFastPath === true) {
996
+ return 'css3d-camera-fast';
997
+ }
998
+
999
+ return 'normal';
1000
+ }
1001
+
1002
+ function updateFramePathStats(module, frameProfile, updatedAt = 0) {
1003
+ if (!module || !frameProfile) {
1004
+ return;
1005
+ }
1006
+
1007
+ const now = Number(
1008
+ updatedAt ||
1009
+ (typeof performance !== 'undefined' && typeof performance.now === 'function'
1010
+ ? performance.now()
1011
+ : Date.now())
1012
+ );
1013
+ const previousStats = module._framePathStats || null;
1014
+ const shouldStartWindow = !previousStats || Number(previousStats.total || 0) >= 600;
1015
+ const stats = shouldStartWindow ? createEmptyFramePathStats(now) : previousStats;
1016
+ const mode = getFrameProfilePathMode(frameProfile);
1017
+ const visibilitySkipReason = String(frameProfile.visibilitySkipReason || '');
1018
+ const lodSkipReason = String(frameProfile.lodUpdateSkipReason || frameProfile.lodSkipReason || '');
1019
+ const viewSyncMode = String(frameProfile.viewSyncMode || '');
1020
+ const hiddenWork =
1021
+ frameProfile.shouldRunVisibilityCulling === true ||
1022
+ frameProfile.lodUpdateCalled === true ||
1023
+ frameProfile.shouldSyncCss3dTransforms === true ||
1024
+ frameProfile.shouldRenderCss3d === true ||
1025
+ frameProfile.shouldSyncTextOverlays === true;
1026
+
1027
+ stats.total += 1;
1028
+ if (frameProfile.uaVisualOnlyMotion === true) {
1029
+ stats.visualOnly += 1;
1030
+ }
1031
+ if (frameProfile.uaStationaryVisualMotion === true) {
1032
+ stats.stationaryVisual += 1;
1033
+ }
1034
+ if (frameProfile.uaResidentVisualOnlyMotion === true ||
1035
+ frameProfile.uaUltraResidentCameraOnly === true) {
1036
+ stats.residentVisual += 1;
1037
+ }
1038
+ if (mode === 'ultra-camera-only' ||
1039
+ mode === 'ultra-resident-camera-only' ||
1040
+ mode === 'early-camera-only' ||
1041
+ mode === 'stationary-camera-navigation') {
1042
+ stats.earlyCameraOnly += 1;
1043
+ }
1044
+ if (frameProfile.usedCss3dCameraFastPath === true) {
1045
+ stats.css3dCameraFast += 1;
1046
+ }
1047
+ if (mode === 'normal') {
1048
+ stats.normal += 1;
1049
+ }
1050
+ if (frameProfile.shouldRunVisibilityCulling === true) {
1051
+ stats.visibilityCulling += 1;
1052
+ }
1053
+ if (frameProfile.lodUpdateCalled === true) {
1054
+ stats.lodUpdateCalled += 1;
1055
+ }
1056
+ if (frameProfile.shouldSyncCss3dTransforms === true) {
1057
+ stats.css3dTransformSync += 1;
1058
+ }
1059
+ if (frameProfile.shouldRenderCss3d === true) {
1060
+ stats.css3dRender += 1;
1061
+ }
1062
+ if (frameProfile.shouldSyncTextOverlays === true) {
1063
+ stats.textOverlaySync += 1;
1064
+ }
1065
+ if (hiddenWork) {
1066
+ stats.hiddenWork += 1;
1067
+ }
1068
+
1069
+ const total = Math.max(1, Number(stats.total || 0));
1070
+ const visualOnlyTotal = Number(stats.visualOnly || 0) +
1071
+ Number(stats.stationaryVisual || 0) +
1072
+ Number(stats.residentVisual || 0) +
1073
+ Number(stats.earlyCameraOnly || 0);
1074
+ stats.visualOnlyPercent = Number((visualOnlyTotal / total * 100).toFixed(1));
1075
+ stats.cameraFastPercent = Number((Number(stats.css3dCameraFast || 0) / total * 100).toFixed(1));
1076
+ stats.normalPercent = Number((Number(stats.normal || 0) / total * 100).toFixed(1));
1077
+ stats.hiddenWorkPercent = Number((Number(stats.hiddenWork || 0) / total * 100).toFixed(1));
1078
+ stats.lastMode = mode;
1079
+ stats.lastReason = visibilitySkipReason || lodSkipReason || viewSyncMode || mode;
1080
+ stats.lastViewSyncMode = viewSyncMode;
1081
+ stats.lastLodSkipReason = lodSkipReason;
1082
+ stats.lastVisibilitySkipReason = visibilitySkipReason;
1083
+ stats.updatedAt = now;
1084
+ module._framePathStats = stats;
1085
+ }
1086
+
854
1087
  function resetFramePerfPeakProfile(module) {
855
1088
  const peakProfile = createEmptyFramePerfPeakProfile();
856
1089
  if (module) {
857
1090
  module._framePerfPeakProfile = peakProfile;
1091
+ module._framePathStats = createEmptyFramePathStats(
1092
+ typeof performance !== 'undefined' && typeof performance.now === 'function'
1093
+ ? performance.now()
1094
+ : Date.now()
1095
+ );
858
1096
  }
859
1097
  return peakProfile;
860
1098
  }
@@ -865,6 +1103,7 @@ const MINDMAP_CORE_BUILD_ID = '20260619-ua-immediate-camera-v629';
865
1103
  }
866
1104
 
867
1105
  const peakProfile = module._framePerfPeakProfile || resetFramePerfPeakProfile(module);
1106
+ updateFramePathStats(module, frameProfile, updatedAt);
868
1107
  let changed = false;
869
1108
  const durationMs = Number(frameProfile.durationMs || 0);
870
1109
  if (durationMs > Number(peakProfile.durationMs || 0)) {
@@ -898,6 +1137,442 @@ const MINDMAP_CORE_BUILD_ID = '20260619-ua-immediate-camera-v629';
898
1137
  }
899
1138
  }
900
1139
 
1140
+ function tryRunUltraEarlyCameraOnlyFrame(module, loop, frameStart, isCameraMoving, shouldCaptureFramePerf, frameProfTimes, profileSection) {
1141
+ if (!module ||
1142
+ CAMERA_MOTION_VISUAL_ONLY_FAST_PATH_ENABLED !== true ||
1143
+ isCameraMoving !== true ||
1144
+ module.isLoading === true ||
1145
+ module.isSelecting === true ||
1146
+ module.isDraggingNode === true ||
1147
+ module.isDraggingMultipleNodes === true ||
1148
+ module.isResizing === true ||
1149
+ module.isWindowResizing === true ||
1150
+ !module.cssRenderer ||
1151
+ typeof module.cssRenderer.renderCamera !== 'function' ||
1152
+ !module.camera) {
1153
+ return false;
1154
+ }
1155
+
1156
+ const renderDebugFlags = module.renderDebugFlags || getRenderDebugFlagsSnapshot();
1157
+ if (renderDebugFlags.enableCss3d === false) {
1158
+ return false;
1159
+ }
1160
+
1161
+ if (module.lodRenderer?.isInLODMode === true) {
1162
+ return false;
1163
+ }
1164
+
1165
+ const forceUpdateFrames = Math.max(0, Number(module._forceUpdateFrames || 0));
1166
+ const cameraNavigationForceFrames = Math.max(0, Number(module._cameraNavigationForceFrames || 0));
1167
+ const panLiveRefreshForceFrames = Math.max(0, Number(module._panLiveRefreshForceFrames || 0));
1168
+ const hasForcedUpdate = forceUpdateFrames > 0;
1169
+ const hasOnlyCameraNavigationForcedUpdate =
1170
+ hasForcedUpdate &&
1171
+ (module.isPanning === true || module.isZooming === true || isCameraMoving === true) &&
1172
+ cameraNavigationForceFrames >= forceUpdateFrames;
1173
+ const hasOnlyPanLiveRefreshForcedUpdate =
1174
+ hasForcedUpdate &&
1175
+ module.isPanning === true &&
1176
+ module.isZooming !== true &&
1177
+ panLiveRefreshForceFrames >= forceUpdateFrames;
1178
+ const hasNonCameraNavigationForcedUpdate =
1179
+ hasForcedUpdate &&
1180
+ hasOnlyCameraNavigationForcedUpdate !== true &&
1181
+ hasOnlyPanLiveRefreshForcedUpdate !== true;
1182
+ if (hasNonCameraNavigationForcedUpdate) {
1183
+ return false;
1184
+ }
1185
+
1186
+ const hasEditingTextOverlayFocus = !!(
1187
+ String(module._textOverlayV2State?.editing?.nodeId || '').trim() ||
1188
+ String(module._editingOverlayState?.nodeId || '').trim()
1189
+ );
1190
+ if (hasEditingTextOverlayFocus) {
1191
+ return false;
1192
+ }
1193
+
1194
+ const hasPendingCss3dStructuralTransformWork = !!(
1195
+ (module._cssTransformDirtyIds?.size || 0) > 0 ||
1196
+ module._cssParentRepairNeeded === true
1197
+ );
1198
+ if (hasPendingCss3dStructuralTransformWork) {
1199
+ return false;
1200
+ }
1201
+
1202
+ const hasPendingCss3dVisualWork = !!(
1203
+ module._css3dVisualChangedThisFrame === true ||
1204
+ module._css3dVisualChangedSinceLastRender === true
1205
+ );
1206
+ if (hasPendingCss3dVisualWork) {
1207
+ module._css3dVisualDeferredForMotion = true;
1208
+ }
1209
+
1210
+ if (hasForcedUpdate) {
1211
+ module._forceUpdateFrames = Math.max(0, forceUpdateFrames - 1);
1212
+ if (cameraNavigationForceFrames > 0) {
1213
+ module._cameraNavigationForceFrames = Math.max(0, cameraNavigationForceFrames - 1);
1214
+ }
1215
+ if (panLiveRefreshForceFrames > 0) {
1216
+ module._panLiveRefreshForceFrames = Math.max(0, panLiveRefreshForceFrames - 1);
1217
+ }
1218
+ }
1219
+
1220
+ module._fullResMotionFrozen = true;
1221
+ module._wasViewStateMotionFrozenLastFrame = true;
1222
+ module._wasLodMotionFrozenLastFrame = false;
1223
+ module._fullResVisibilityReconcilePending = true;
1224
+ module._preserveVisibleSetThisFrame = true;
1225
+ module._lastVisibilityCullingSkipReason = 'ua-ultra-camera-only';
1226
+ if (module._overlayDirty === true ||
1227
+ String(module._textOverlayV2State?.selectionNodeId || '').trim() ||
1228
+ String(module._selectableTextOverlayNodeId || '').trim()) {
1229
+ module._deferPassiveOverlayRefresh = true;
1230
+ }
1231
+
1232
+ if (shouldSyncMindCanvasPerfClasses()) {
1233
+ const visibleNodeCount = Number(module._visibleIds?.size || module._prevVisibleIds?.size || 0);
1234
+ const totalNodeCount = Number(module.nodeObjectsById?.size || 0);
1235
+ syncMindCanvasPerfClasses({
1236
+ moving: true,
1237
+ dense:
1238
+ visibleNodeCount >= DENSE_VISIBLE_NODE_THRESHOLD ||
1239
+ totalNodeCount >= DENSE_TOTAL_NODE_THRESHOLD
1240
+ });
1241
+ }
1242
+
1243
+ updateCursorDomElement(module);
1244
+
1245
+ const isWebglRenderEnabled = renderDebugFlags.enableWebglRender !== false;
1246
+ let usedWebglCanvasCameraTransform = false;
1247
+ if (isWebglRenderEnabled && module.renderer && module.scene && module.camera) {
1248
+ const canReuseWebglCanvas = !!(
1249
+ canUseWebglCanvasTransformForCameraMotion(module) === true &&
1250
+ module._lastWebglRenderedCameraState &&
1251
+ module.renderer?.domElement
1252
+ );
1253
+ if (canReuseWebglCanvas === true) {
1254
+ deferContinuousThemeForCameraMotion(module);
1255
+ usedWebglCanvasCameraTransform = module._applyWebglCanvasCameraTransform?.() === true;
1256
+ module._webglCameraMotionDeferred = true;
1257
+ module._lastWebglRenderInfo = {
1258
+ calls: 0,
1259
+ triangles: 0,
1260
+ points: 0,
1261
+ lines: 0,
1262
+ skipped: true,
1263
+ webglCanvasCameraTransform: usedWebglCanvasCameraTransform === true,
1264
+ uaUltraEarlyCameraOnly: true
1265
+ };
1266
+ } else {
1267
+ module._clearWebglCanvasCameraTransform?.();
1268
+ module.renderer.render(module.scene, module.camera);
1269
+ module._recordWebglRenderedCameraState?.();
1270
+ const renderInfo = module.renderer?.info?.render || null;
1271
+ module._lastWebglRenderInfo = renderInfo ? {
1272
+ calls: Number(renderInfo.calls || 0),
1273
+ triangles: Number(renderInfo.triangles || 0),
1274
+ points: Number(renderInfo.points || 0),
1275
+ lines: Number(renderInfo.lines || 0),
1276
+ uaUltraEarlyCameraOnly: true,
1277
+ webglCanvasCameraBootstrap: true
1278
+ } : null;
1279
+ }
1280
+ }
1281
+
1282
+ module._syncMotionGridLayerForCameraNavigation?.(usedWebglCanvasCameraTransform);
1283
+ module.cssRenderer.renderCamera(module.camera);
1284
+
1285
+ if ((module._businessAutomationEdgeVisualLayer?.isConnected ||
1286
+ module._businessAutomationEdgeLayer?.isConnected) &&
1287
+ window.MindMapCss3DManager?.syncBusinessAutomationEdgesForCameraMotion) {
1288
+ const transformedEdges =
1289
+ window.MindMapCss3DManager.syncBusinessAutomationEdgesForCameraMotion(module) === true;
1290
+ module._businessAutomationEdgesDeferredForMotion = transformedEdges !== false;
1291
+ }
1292
+
1293
+ profileSection?.('uaUltraEarlyCameraOnly');
1294
+
1295
+ const frameEnd = performance.now();
1296
+ const frameDuration = frameEnd - frameStart;
1297
+ module._lastFrameDurationMs = frameDuration;
1298
+ module._lastFrameMetricsAt = frameEnd;
1299
+ if (shouldCaptureFramePerf) {
1300
+ const sectionTimes = {};
1301
+ Object.entries(frameProfTimes || {})
1302
+ .filter(([key, value]) => key !== 'visibleNodes' && typeof value === 'number')
1303
+ .forEach(([key, value]) => {
1304
+ sectionTimes[key] = Number(value.toFixed(2));
1305
+ });
1306
+ module._lastFramePerfProfile = {
1307
+ durationMs: Number(frameDuration.toFixed(2)),
1308
+ update: false,
1309
+ cameraZ: Number((module.camera?.position?.z || 0).toFixed(2)),
1310
+ isZooming: module.isZooming === true,
1311
+ isPanning: module.isPanning === true,
1312
+ isCameraMoving: true,
1313
+ isInLodMode: false,
1314
+ lodBand: module.lodRenderer?._currentLodBand || null,
1315
+ visibleNodes: Number(module._visibleIds?.size || module._prevVisibleIds?.size || 0),
1316
+ freezeVisibility: true,
1317
+ freezeLod: false,
1318
+ freezeFullRes: true,
1319
+ residentMidFarStable: false,
1320
+ residentDirty: false,
1321
+ residentBandChanging: false,
1322
+ viewSyncMode: 'ua-ultra-camera-only',
1323
+ lodUpdateCalled: false,
1324
+ lodUpdateSkipped: true,
1325
+ lodUpdateSkipReason: 'ua-ultra-camera-only',
1326
+ lodSkipReason: '',
1327
+ lodDirtyReason: '',
1328
+ lodDirtyState: null,
1329
+ viewSyncBreakdown: null,
1330
+ shouldRunVisibilityCulling: false,
1331
+ visibilityStableSkip: true,
1332
+ visibilitySkipReason: 'ua-ultra-camera-only',
1333
+ incrementalPanVisibility: false,
1334
+ shouldSyncCss3dTransforms: false,
1335
+ shouldRenderCss3d: false,
1336
+ usedCss3dCameraFastPath: true,
1337
+ uaUltraEarlyCameraOnly: true,
1338
+ shouldSyncTextOverlays: false,
1339
+ usedPassiveOverlayCameraFastPath: false,
1340
+ passiveOverlaySuppressed: module._suppressPassiveDomOverlayDuringZoom === true,
1341
+ webgl: module._lastWebglRenderInfo || null,
1342
+ sections: sectionTimes
1343
+ };
1344
+ updateFramePerfPeakProfile(module, module._lastFramePerfProfile, frameEnd);
1345
+ } else if (module._lastFramePerfProfile) {
1346
+ module._lastFramePerfProfile = null;
1347
+ }
1348
+
1349
+ module._lastAnimationLoopDelayMs = 0;
1350
+ module._animationLoopExecuting = false;
1351
+ module._scheduleAnimationLoop(loop, 0);
1352
+ return true;
1353
+ }
1354
+
1355
+ function tryRunUltraEarlyResidentCameraOnlyFrame(module, loop, frameStart, isCameraMoving, shouldCaptureFramePerf, frameProfTimes, profileSection) {
1356
+ if (!module ||
1357
+ CAMERA_MOTION_VISUAL_ONLY_FAST_PATH_ENABLED !== true ||
1358
+ isCameraMoving !== true ||
1359
+ module.isLoading === true ||
1360
+ module.isSelecting === true ||
1361
+ module.isDraggingNode === true ||
1362
+ module.isDraggingMultipleNodes === true ||
1363
+ module.isResizing === true ||
1364
+ module.isWindowResizing === true ||
1365
+ !module.camera ||
1366
+ !module.lodRenderer ||
1367
+ module.lodRenderer.isInLODMode !== true ||
1368
+ typeof module.lodRenderer.canUseUltraResidentCameraOnlyFrame !== 'function' ||
1369
+ module.lodRenderer.canUseUltraResidentCameraOnlyFrame(module.camera, module) !== true) {
1370
+ return false;
1371
+ }
1372
+
1373
+ const renderDebugFlags = module.renderDebugFlags || getRenderDebugFlagsSnapshot();
1374
+ const isWebglRenderEnabled = renderDebugFlags.enableWebglRender !== false;
1375
+ const isCss3dEnabled = renderDebugFlags.enableCss3d !== false;
1376
+ const canRenderCss3dCamera = !!(
1377
+ isCss3dEnabled === true &&
1378
+ module.cssRenderer &&
1379
+ typeof module.cssRenderer.renderCamera === 'function' &&
1380
+ module.lodRenderer.hasCss3dLodCandidates?.() === true
1381
+ );
1382
+ if (isWebglRenderEnabled !== true && canRenderCss3dCamera !== true) {
1383
+ return false;
1384
+ }
1385
+
1386
+ const forceUpdateFrames = Math.max(0, Number(module._forceUpdateFrames || 0));
1387
+ const cameraNavigationForceFrames = Math.max(0, Number(module._cameraNavigationForceFrames || 0));
1388
+ const panLiveRefreshForceFrames = Math.max(0, Number(module._panLiveRefreshForceFrames || 0));
1389
+ const hasForcedUpdate = forceUpdateFrames > 0;
1390
+ const hasOnlyCameraNavigationForcedUpdate =
1391
+ hasForcedUpdate &&
1392
+ (module.isPanning === true || module.isZooming === true || isCameraMoving === true) &&
1393
+ cameraNavigationForceFrames >= forceUpdateFrames;
1394
+ const hasOnlyPanLiveRefreshForcedUpdate =
1395
+ hasForcedUpdate &&
1396
+ module.isPanning === true &&
1397
+ module.isZooming !== true &&
1398
+ panLiveRefreshForceFrames >= forceUpdateFrames;
1399
+ const hasNonCameraNavigationForcedUpdate =
1400
+ hasForcedUpdate &&
1401
+ hasOnlyCameraNavigationForcedUpdate !== true &&
1402
+ hasOnlyPanLiveRefreshForcedUpdate !== true;
1403
+ if (hasNonCameraNavigationForcedUpdate) {
1404
+ return false;
1405
+ }
1406
+
1407
+ const hasEditingTextOverlayFocus = !!(
1408
+ String(module._textOverlayV2State?.editing?.nodeId || '').trim() ||
1409
+ String(module._editingOverlayState?.nodeId || '').trim()
1410
+ );
1411
+ if (hasEditingTextOverlayFocus) {
1412
+ return false;
1413
+ }
1414
+
1415
+ const hasPendingCss3dStructuralTransformWork = !!(
1416
+ (module._cssTransformDirtyIds?.size || 0) > 0 ||
1417
+ module._cssParentRepairNeeded === true
1418
+ );
1419
+ if (hasPendingCss3dStructuralTransformWork) {
1420
+ return false;
1421
+ }
1422
+
1423
+ const hasPendingCss3dVisualWork = !!(
1424
+ module._css3dVisualChangedThisFrame === true ||
1425
+ module._css3dVisualChangedSinceLastRender === true
1426
+ );
1427
+ if (hasPendingCss3dVisualWork) {
1428
+ module._css3dVisualDeferredForMotion = true;
1429
+ }
1430
+
1431
+ if (hasForcedUpdate) {
1432
+ module._forceUpdateFrames = Math.max(0, forceUpdateFrames - 1);
1433
+ if (cameraNavigationForceFrames > 0) {
1434
+ module._cameraNavigationForceFrames = Math.max(0, cameraNavigationForceFrames - 1);
1435
+ }
1436
+ if (panLiveRefreshForceFrames > 0) {
1437
+ module._panLiveRefreshForceFrames = Math.max(0, panLiveRefreshForceFrames - 1);
1438
+ }
1439
+ }
1440
+
1441
+ module._fullResMotionFrozen = false;
1442
+ module._wasViewStateMotionFrozenLastFrame = false;
1443
+ module._wasLodMotionFrozenLastFrame = true;
1444
+ module._preserveVisibleSetThisFrame = true;
1445
+ module._lastVisibilityCullingSkipReason = 'ua-ultra-resident-camera-only';
1446
+ if (module._overlayDirty === true ||
1447
+ String(module._textOverlayV2State?.selectionNodeId || '').trim() ||
1448
+ String(module._selectableTextOverlayNodeId || '').trim()) {
1449
+ module._deferPassiveOverlayRefresh = true;
1450
+ }
1451
+
1452
+ if (shouldSyncMindCanvasPerfClasses()) {
1453
+ const visibleNodeCount = Number(module._visibleIds?.size || module._prevVisibleIds?.size || 0);
1454
+ const totalNodeCount = Number(module.nodeObjectsById?.size || 0);
1455
+ syncMindCanvasPerfClasses({
1456
+ moving: true,
1457
+ dense:
1458
+ visibleNodeCount >= DENSE_VISIBLE_NODE_THRESHOLD ||
1459
+ totalNodeCount >= DENSE_TOTAL_NODE_THRESHOLD
1460
+ });
1461
+ }
1462
+
1463
+ updateCursorDomElement(module);
1464
+
1465
+ let usedWebglCanvasCameraTransform = false;
1466
+ if (isWebglRenderEnabled === true && module.renderer && module.scene && module.camera) {
1467
+ const canReuseWebglCanvas = !!(
1468
+ canUseWebglCanvasTransformForCameraMotion(module) === true &&
1469
+ module._lastWebglRenderedCameraState &&
1470
+ module.renderer?.domElement
1471
+ );
1472
+ if (canReuseWebglCanvas === true) {
1473
+ deferContinuousThemeForCameraMotion(module);
1474
+ usedWebglCanvasCameraTransform = module._applyWebglCanvasCameraTransform?.() === true;
1475
+ module._webglCameraMotionDeferred = true;
1476
+ module._lastWebglRenderInfo = {
1477
+ calls: 0,
1478
+ triangles: 0,
1479
+ points: 0,
1480
+ lines: 0,
1481
+ skipped: true,
1482
+ webglCanvasCameraTransform: usedWebglCanvasCameraTransform === true,
1483
+ uaUltraResidentCameraOnly: true
1484
+ };
1485
+ } else {
1486
+ module._clearWebglCanvasCameraTransform?.();
1487
+ module.renderer.render(module.scene, module.camera);
1488
+ module._recordWebglRenderedCameraState?.();
1489
+ const renderInfo = module.renderer?.info?.render || null;
1490
+ module._lastWebglRenderInfo = renderInfo ? {
1491
+ calls: Number(renderInfo.calls || 0),
1492
+ triangles: Number(renderInfo.triangles || 0),
1493
+ points: Number(renderInfo.points || 0),
1494
+ lines: Number(renderInfo.lines || 0),
1495
+ uaUltraResidentCameraOnly: true,
1496
+ webglCanvasCameraBootstrap: true
1497
+ } : null;
1498
+ }
1499
+ }
1500
+
1501
+ module._syncMotionGridLayerForCameraNavigation?.(usedWebglCanvasCameraTransform);
1502
+ if (canRenderCss3dCamera === true) {
1503
+ module.cssRenderer.renderCamera(module.camera);
1504
+ }
1505
+
1506
+ if ((module._businessAutomationEdgeVisualLayer?.isConnected ||
1507
+ module._businessAutomationEdgeLayer?.isConnected) &&
1508
+ window.MindMapCss3DManager?.syncBusinessAutomationEdgesForCameraMotion) {
1509
+ const transformedEdges =
1510
+ window.MindMapCss3DManager.syncBusinessAutomationEdgesForCameraMotion(module) === true;
1511
+ module._businessAutomationEdgesDeferredForMotion = transformedEdges !== false;
1512
+ }
1513
+
1514
+ profileSection?.('uaUltraResidentCameraOnly');
1515
+
1516
+ const frameEnd = performance.now();
1517
+ const frameDuration = frameEnd - frameStart;
1518
+ module._lastFrameDurationMs = frameDuration;
1519
+ module._lastFrameMetricsAt = frameEnd;
1520
+ if (shouldCaptureFramePerf) {
1521
+ const sectionTimes = {};
1522
+ Object.entries(frameProfTimes || {})
1523
+ .filter(([key, value]) => key !== 'visibleNodes' && typeof value === 'number')
1524
+ .forEach(([key, value]) => {
1525
+ sectionTimes[key] = Number(value.toFixed(2));
1526
+ });
1527
+ module._lastFramePerfProfile = {
1528
+ durationMs: Number(frameDuration.toFixed(2)),
1529
+ update: false,
1530
+ cameraZ: Number((module.camera?.position?.z || 0).toFixed(2)),
1531
+ isZooming: module.isZooming === true,
1532
+ isPanning: module.isPanning === true,
1533
+ isCameraMoving: true,
1534
+ isInLodMode: true,
1535
+ lodBand: module.lodRenderer?._currentLodBand || null,
1536
+ visibleNodes: Number(module._visibleIds?.size || module._prevVisibleIds?.size || 0),
1537
+ freezeVisibility: true,
1538
+ freezeLod: true,
1539
+ freezeFullRes: false,
1540
+ residentMidFarStable: true,
1541
+ residentDirty: false,
1542
+ residentBandChanging: false,
1543
+ viewSyncMode: 'ua-ultra-resident-camera-only',
1544
+ lodUpdateCalled: false,
1545
+ lodUpdateSkipped: true,
1546
+ lodUpdateSkipReason: 'ua-ultra-resident-camera-only',
1547
+ lodSkipReason: '',
1548
+ lodDirtyReason: '',
1549
+ lodDirtyState: null,
1550
+ viewSyncBreakdown: null,
1551
+ shouldRunVisibilityCulling: false,
1552
+ visibilityStableSkip: true,
1553
+ visibilitySkipReason: 'ua-ultra-resident-camera-only',
1554
+ incrementalPanVisibility: false,
1555
+ shouldSyncCss3dTransforms: false,
1556
+ shouldRenderCss3d: false,
1557
+ usedCss3dCameraFastPath: canRenderCss3dCamera === true,
1558
+ uaUltraResidentCameraOnly: true,
1559
+ shouldSyncTextOverlays: false,
1560
+ usedPassiveOverlayCameraFastPath: false,
1561
+ passiveOverlaySuppressed: module._suppressPassiveDomOverlayDuringZoom === true,
1562
+ webgl: module._lastWebglRenderInfo || null,
1563
+ sections: sectionTimes
1564
+ };
1565
+ updateFramePerfPeakProfile(module, module._lastFramePerfProfile, frameEnd);
1566
+ } else if (module._lastFramePerfProfile) {
1567
+ module._lastFramePerfProfile = null;
1568
+ }
1569
+
1570
+ module._lastAnimationLoopDelayMs = 0;
1571
+ module._animationLoopExecuting = false;
1572
+ module._scheduleAnimationLoop(loop, 0);
1573
+ return true;
1574
+ }
1575
+
901
1576
  function getRenderDebugMetricsSnapshot() {
902
1577
  const snapshotStart = (typeof performance !== 'undefined' && typeof performance.now === 'function')
903
1578
  ? performance.now()
@@ -910,6 +1585,7 @@ const MINDMAP_CORE_BUILD_ID = '20260619-ua-immediate-camera-v629';
910
1585
  lastFrameDurationMs: 0,
911
1586
  lastFramePerfProfile: null,
912
1587
  lastFramePerfPeakProfile: null,
1588
+ framePathStats: null,
913
1589
  updatedAt: 0,
914
1590
  visibleNodes: 0,
915
1591
  isZooming: false,
@@ -985,9 +1661,24 @@ const MINDMAP_CORE_BUILD_ID = '20260619-ua-immediate-camera-v629';
985
1661
  : (flags.enablePassiveOverlayForVisibleNodes !== false
986
1662
  ? 'all-visible passive'
987
1663
  : 'selected/editing only');
988
- const residentDirtyState = typeof moduleInstance.lodRenderer?.getResidentDirtyState === 'function'
1664
+ const shouldBuildResidentDirtySnapshot =
1665
+ flags.enableFramePerfProbe === true ||
1666
+ flags.enableNodeFrameDebug === true;
1667
+ const residentDirtyState = shouldBuildResidentDirtySnapshot === true &&
1668
+ typeof moduleInstance.lodRenderer?.getResidentDirtyState === 'function'
989
1669
  ? moduleInstance.lodRenderer.getResidentDirtyState(moduleInstance.camera, moduleInstance)
990
- : null;
1670
+ : (
1671
+ moduleInstance.lodRenderer
1672
+ ? {
1673
+ dirty: moduleInstance.lodRenderer.canUseUltraResidentCameraOnlyFrame?.(moduleInstance.camera, moduleInstance) !== true,
1674
+ approximate: true,
1675
+ lodBand: currentLodBand || '',
1676
+ currentLodBand,
1677
+ hasResidentInstances: moduleInstance.lodRenderer._hasResidentLodInstances?.() === true,
1678
+ detailedSnapshotSkipped: true
1679
+ }
1680
+ : null
1681
+ );
991
1682
 
992
1683
  const snapshotEnd = (typeof performance !== 'undefined' && typeof performance.now === 'function')
993
1684
  ? performance.now()
@@ -1007,6 +1698,7 @@ const MINDMAP_CORE_BUILD_ID = '20260619-ua-immediate-camera-v629';
1007
1698
  lastFrameDurationMs: Number(moduleInstance._lastFrameDurationMs || 0),
1008
1699
  lastFramePerfProfile: moduleInstance._lastFramePerfProfile || null,
1009
1700
  lastFramePerfPeakProfile: moduleInstance._framePerfPeakProfile || null,
1701
+ framePathStats: moduleInstance._framePathStats || null,
1010
1702
  updatedAt: Number(moduleInstance._lastFrameMetricsAt || 0),
1011
1703
  visibleNodes: Number(moduleInstance._visibleIds?.size || moduleInstance._prevVisibleIds?.size || 0),
1012
1704
  isZooming: moduleInstance.isZooming === true,
@@ -2583,6 +3275,9 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
2583
3275
  this.lodUpdateQueue = []; // Initialize lodUpdateQueue
2584
3276
  this.isPanning = false;
2585
3277
  this._currentDpr = null;
3278
+ this._lastWebglRenderedCameraState = null;
3279
+ this._webglCanvasMotionTransformActive = false;
3280
+ this._lastWebglCanvasMotionTransformKey = '';
2586
3281
  this._lastViewportWidth = 0;
2587
3282
  this._lastViewportHeight = 0;
2588
3283
  this._lastViewportDpr = 0;
@@ -2620,6 +3315,7 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
2620
3315
  this._pendingViewStateSnapshot = null;
2621
3316
  this._pendingViewStateKey = null;
2622
3317
  this._lastPersistedViewStateKey = null;
3318
+ this._viewStatePersistNeedsFreshSnapshot = false;
2623
3319
  this._viewStatePersistDelayMs = 650;
2624
3320
  this._viewStatePersistScheduledAt = 0;
2625
3321
  this._postLoadVisualSyncToken = 0;
@@ -2719,6 +3415,13 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
2719
3415
  this._installForceUpdateWakeHook();
2720
3416
  this._forceUpdateFrames = 0;
2721
3417
  this._forceThemeAnimationFrame = false;
3418
+ this._webglCameraMotionDeferred = false;
3419
+ this._themeCameraMotionDeferred = false;
3420
+ this._css3dVisualDeferredForMotion = false;
3421
+ this._motionGridLayer = null;
3422
+ this._motionGridVisible = false;
3423
+ this._motionGridHideTimer = null;
3424
+ this._lastMotionGridKey = '';
2722
3425
  this._animationIdleTimerId = null;
2723
3426
  this._animationLoopCallback = null;
2724
3427
  this._animationLoopExecuting = false;
@@ -2758,6 +3461,195 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
2758
3461
  // ▼▼▼ [New] Node snap function - use Math.round to snap to nearest grid ▼▼▼
2759
3462
  snapNodeToGrid(v) { return Math.round(v / this.GRID_SIZE) * this.GRID_SIZE; }
2760
3463
 
3464
+ _isMotionGridThemeEnabled() {
3465
+ const themeName = String(this.currentThemeName || '').trim();
3466
+ return themeName === 'SpatialGrid2D' ||
3467
+ themeName === 'XYGrid' ||
3468
+ themeName === 'GridPlane' ||
3469
+ themeName === 'Space3D' ||
3470
+ themeName === 'SpiritFlow' ||
3471
+ themeName === 'SpinningBoxes';
3472
+ }
3473
+
3474
+ _ensureMotionGridLayer() {
3475
+ if (!this.container) {
3476
+ return null;
3477
+ }
3478
+ if (this._motionGridLayer?.isConnected) {
3479
+ return this._motionGridLayer;
3480
+ }
3481
+
3482
+ const layer = document.createElement('div');
3483
+ layer.className = 'mindcanvas-motion-grid-layer';
3484
+ layer.setAttribute('aria-hidden', 'true');
3485
+ layer.style.position = 'absolute';
3486
+ layer.style.inset = `-${CAMERA_MOTION_GRID_OVERSCAN_PX}px`;
3487
+ layer.style.zIndex = '0';
3488
+ layer.style.pointerEvents = 'none';
3489
+ layer.style.display = 'none';
3490
+ layer.style.opacity = '0';
3491
+ layer.style.backgroundRepeat = 'repeat';
3492
+ layer.style.contain = 'strict';
3493
+ layer.style.willChange = 'transform, opacity';
3494
+ layer.style.transform = 'translate3d(0, 0, 0)';
3495
+ layer.style.transition = 'none';
3496
+
3497
+ const cssElement = this.cssRenderer?.domElement;
3498
+ if (cssElement?.parentNode === this.container) {
3499
+ this.container.insertBefore(layer, cssElement);
3500
+ } else {
3501
+ this.container.appendChild(layer);
3502
+ }
3503
+
3504
+ this._motionGridLayer = layer;
3505
+ this._lastMotionGridKey = '';
3506
+ this._lastMotionGridTransformKey = '';
3507
+ return layer;
3508
+ }
3509
+
3510
+ _setMotionGridVisible(visible) {
3511
+ const layer = this._motionGridLayer;
3512
+ if (!layer) {
3513
+ return;
3514
+ }
3515
+
3516
+ if (visible === true) {
3517
+ if (this._motionGridHideTimer) {
3518
+ clearTimeout(this._motionGridHideTimer);
3519
+ this._motionGridHideTimer = null;
3520
+ }
3521
+ if (this._motionGridVisible === true) {
3522
+ if (layer.style.display !== 'block') {
3523
+ layer.style.display = 'block';
3524
+ }
3525
+ return;
3526
+ }
3527
+ this._motionGridVisible = true;
3528
+ if (layer.style.display !== 'block') {
3529
+ layer.style.display = 'block';
3530
+ }
3531
+ return;
3532
+ }
3533
+
3534
+ if (this._motionGridVisible !== true) {
3535
+ return;
3536
+ }
3537
+
3538
+ this._motionGridVisible = false;
3539
+ if (layer.style.opacity !== '0') {
3540
+ layer.style.opacity = '0';
3541
+ }
3542
+ if (this._motionGridHideTimer) {
3543
+ return;
3544
+ }
3545
+ this._motionGridHideTimer = setTimeout(() => {
3546
+ this._motionGridHideTimer = null;
3547
+ if (this._motionGridVisible !== true && layer.isConnected) {
3548
+ layer.style.display = 'none';
3549
+ }
3550
+ }, CAMERA_MOTION_GRID_HIDE_DELAY_MS);
3551
+ }
3552
+
3553
+ _syncMotionGridLayer(visible = true) {
3554
+ if (visible !== true) {
3555
+ this._setMotionGridVisible(false);
3556
+ return false;
3557
+ }
3558
+
3559
+ if (!this._isMotionGridThemeEnabled() || !this.container || !this.camera?.isPerspectiveCamera) {
3560
+ this._setMotionGridVisible(false);
3561
+ return false;
3562
+ }
3563
+
3564
+ const layer = this._ensureMotionGridLayer();
3565
+ if (!layer) {
3566
+ return false;
3567
+ }
3568
+
3569
+ const viewportMetrics = getCachedViewportMetrics(this);
3570
+ const width = Math.max(1, Number(viewportMetrics.width || 0));
3571
+ const height = Math.max(1, Number(viewportMetrics.height || 0));
3572
+ const camera = this.camera;
3573
+ const rawCameraZ = Math.max(1, Number(camera.position?.z || 1));
3574
+ const isCameraNavigationFrame =
3575
+ this.isPanning === true ||
3576
+ this.isZooming === true ||
3577
+ this._webglCameraMotionDeferred === true;
3578
+ const cameraZ = isCameraNavigationFrame
3579
+ ? Math.max(1, Math.round(rawCameraZ / CAMERA_MOTION_GRID_Z_BUCKET_WORLD) * CAMERA_MOTION_GRID_Z_BUCKET_WORLD)
3580
+ : rawCameraZ;
3581
+ const vfov = (Number(camera.fov || 45) * Math.PI) / 180;
3582
+ const viewHeight = 2 * Math.tan(vfov / 2) * cameraZ;
3583
+ const pxPerWorld = height / Math.max(1, viewHeight);
3584
+ const worldGrid = Math.max(1, Number(this.GRID_SIZE || 10));
3585
+ const smallStepPx = Math.max(
3586
+ CAMERA_MOTION_GRID_MIN_STEP_PX,
3587
+ Math.min(CAMERA_MOTION_GRID_MAX_STEP_PX, worldGrid * pxPerWorld)
3588
+ );
3589
+ const majorStepPx = smallStepPx * 10;
3590
+ const cameraX = Number(camera.position?.x || 0);
3591
+ const cameraY = Number(camera.position?.y || 0);
3592
+ const themeName = String(this.currentThemeName || '').trim();
3593
+ const isParticleMotionTheme =
3594
+ themeName === 'Space3D' ||
3595
+ themeName === 'SpiritFlow' ||
3596
+ themeName === 'SpinningBoxes';
3597
+ const originX = width / 2 - cameraX * pxPerWorld;
3598
+ const originY = height / 2 + cameraY * pxPerWorld;
3599
+ const positiveModulo = (value, size) => ((value % size) + size) % size;
3600
+ const translateX = positiveModulo(originX + CAMERA_MOTION_GRID_OVERSCAN_PX, majorStepPx);
3601
+ const translateY = positiveModulo(originY + CAMERA_MOTION_GRID_OVERSCAN_PX, majorStepPx);
3602
+ const opacity = isParticleMotionTheme
3603
+ ? 0.96
3604
+ : (cameraZ <= 1600
3605
+ ? 1
3606
+ : Math.max(0.38, 1 - ((cameraZ - 1600) / 12000)));
3607
+ const backgroundColor = themeName === 'GridPlane' || themeName === 'XYGrid'
3608
+ ? '#e8edf3'
3609
+ : '#e3e7ee';
3610
+ const motionGridKey = [
3611
+ width,
3612
+ height,
3613
+ themeName,
3614
+ Math.round(cameraZ),
3615
+ Math.round(smallStepPx * 100),
3616
+ Math.round(majorStepPx * 100),
3617
+ Math.round(opacity * 100)
3618
+ ].join('|');
3619
+
3620
+ if (motionGridKey !== this._lastMotionGridKey) {
3621
+ this._lastMotionGridKey = motionGridKey;
3622
+ layer.style.backgroundColor = backgroundColor;
3623
+ layer.style.backgroundImage = [
3624
+ 'radial-gradient(circle at 0 0, rgba(94, 103, 115, 0.34) 1px, transparent 1.45px)',
3625
+ 'radial-gradient(circle at 0 0, rgba(77, 86, 99, 0.52) 1.8px, transparent 2.2px)'
3626
+ ].join(', ');
3627
+ layer.style.backgroundSize = `${smallStepPx.toFixed(3)}px ${smallStepPx.toFixed(3)}px, ${majorStepPx.toFixed(3)}px ${majorStepPx.toFixed(3)}px`;
3628
+ layer.style.backgroundPosition = '0 0, 0 0';
3629
+ layer.style.opacity = opacity.toFixed(3);
3630
+ }
3631
+ const transformKey = `${Math.round(translateX * 100)}|${Math.round(translateY * 100)}`;
3632
+ if (transformKey !== this._lastMotionGridTransformKey) {
3633
+ this._lastMotionGridTransformKey = transformKey;
3634
+ layer.style.transform = `translate3d(${translateX.toFixed(3)}px, ${translateY.toFixed(3)}px, 0)`;
3635
+ }
3636
+
3637
+ this._setMotionGridVisible(opacity > 0.01);
3638
+ return opacity > 0.01;
3639
+ }
3640
+
3641
+ _syncMotionGridLayerForCameraNavigation(usedWebglCanvasCameraTransform = false) {
3642
+ if (usedWebglCanvasCameraTransform === true ||
3643
+ CAMERA_MOTION_GRID_FALLBACK_ENABLED !== true) {
3644
+ if (this._motionGridVisible === true || this._motionGridLayer) {
3645
+ this._syncMotionGridLayer(false);
3646
+ }
3647
+ return false;
3648
+ }
3649
+
3650
+ return this._syncMotionGridLayer(true);
3651
+ }
3652
+
2761
3653
  _installForceUpdateWakeHook() {
2762
3654
  if (Object.getOwnPropertyDescriptor(this, '_forceUpdateFrames')?.get) {
2763
3655
  return;
@@ -3187,9 +4079,12 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
3187
4079
  console.error('[mind-map-core.js] Container not found!');
3188
4080
  return;
3189
4081
  }
3190
- this.container.style.display = 'block';
3191
- this._lastContainerWidth = this.container.clientWidth || 0;
3192
- this._lastContainerHeight = this.container.clientHeight || 0;
4082
+ this.container.style.display = 'block';
4083
+ this._lastContainerWidth = this.container.clientWidth || 0;
4084
+ this._lastContainerHeight = this.container.clientHeight || 0;
4085
+ this._lastViewportWidth = this._lastContainerWidth;
4086
+ this._lastViewportHeight = this._lastContainerHeight;
4087
+ this._lastViewportDpr = Math.max(1, Number(window.devicePixelRatio || 1));
3193
4088
  const THREE = globalThis.THREE;
3194
4089
  this.scene = new THREE.Scene();
3195
4090
  this.cssScene = new THREE.Scene();
@@ -3425,10 +4320,11 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
3425
4320
  const newTheme = window.MindMapThemes.getTheme(themeName);
3426
4321
  if (!newTheme) { console.warn(`[mind-map-core.js] Theme '${themeName}' not found.`); return; }
3427
4322
  if (this.currentThemeObject) {
3428
- const oldTheme = window.MindMapThemes.getTheme(this.currentThemeName);
3429
- try { oldTheme?.dispose(this.currentThemeObject); } catch { }
3430
- if (this.scene) this.scene.remove(this.currentThemeObject);
3431
- }
4323
+ const oldTheme = window.MindMapThemes.getTheme(this.currentThemeName);
4324
+ try { oldTheme?.dispose(this.currentThemeObject); } catch { }
4325
+ if (this.scene) this.scene.remove(this.currentThemeObject);
4326
+ }
4327
+ this._syncMotionGridLayer(false);
3432
4328
  this.currentThemeObject = newTheme.create(this.scene, this.renderer);
3433
4329
  if (this.currentThemeObject) this.scene.add(this.currentThemeObject);
3434
4330
  this.currentThemeName = themeName;
@@ -3492,6 +4388,76 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
3492
4388
  this.renderer.setSize(width, height, false);
3493
4389
  }
3494
4390
 
4391
+ _recordWebglRenderedCameraState() {
4392
+ if (!this.camera) {
4393
+ return;
4394
+ }
4395
+
4396
+ const viewportMetrics = getCachedViewportMetrics(this);
4397
+ this._lastWebglRenderedCameraState = {
4398
+ x: Number(this.camera.position?.x || 0),
4399
+ y: Number(this.camera.position?.y || 0),
4400
+ z: Math.max(1, Number(this.camera.position?.z || 1)),
4401
+ width: Math.max(1, Number(viewportMetrics.width || 1)),
4402
+ height: Math.max(1, Number(viewportMetrics.height || 1)),
4403
+ fov: Number(this.camera.fov || 45)
4404
+ };
4405
+ }
4406
+
4407
+ _clearWebglCanvasCameraTransform() {
4408
+ const element = this.renderer?.domElement;
4409
+ if (!element || this._webglCanvasMotionTransformActive !== true) {
4410
+ return false;
4411
+ }
4412
+
4413
+ element.style.transform = '';
4414
+ element.style.transformOrigin = '';
4415
+ element.style.willChange = '';
4416
+ this._webglCanvasMotionTransformActive = false;
4417
+ this._lastWebglCanvasMotionTransformKey = '';
4418
+ return true;
4419
+ }
4420
+
4421
+ _applyWebglCanvasCameraTransform() {
4422
+ const element = this.renderer?.domElement;
4423
+ const camera = this.camera;
4424
+ const last = this._lastWebglRenderedCameraState;
4425
+ if (!element || !camera || !last) {
4426
+ return false;
4427
+ }
4428
+
4429
+ const viewportMetrics = getCachedViewportMetrics(this);
4430
+ const width = Math.max(1, Number(viewportMetrics.width || last.width || 1));
4431
+ const height = Math.max(1, Number(viewportMetrics.height || last.height || 1));
4432
+ const currentZ = Math.max(1, Number(camera.position?.z || 1));
4433
+ const lastZ = Math.max(1, Number(last.z || currentZ));
4434
+ const vfov = (Number(camera.fov || last.fov || 45) * Math.PI) / 180;
4435
+ const viewHeight = 2 * Math.tan(vfov / 2) * currentZ;
4436
+ const pxPerWorld = height / Math.max(1, viewHeight);
4437
+ const scale = Math.max(0.05, Math.min(20, lastZ / currentZ));
4438
+ const currentX = Number(camera.position?.x || 0);
4439
+ const currentY = Number(camera.position?.y || 0);
4440
+ const translateX = (Number(last.x || 0) - currentX) * pxPerWorld;
4441
+ const translateY = (currentY - Number(last.y || 0)) * pxPerWorld;
4442
+ const transformKey = [
4443
+ Math.round(width),
4444
+ Math.round(height),
4445
+ Math.round(translateX * 100),
4446
+ Math.round(translateY * 100),
4447
+ Math.round(scale * 10000)
4448
+ ].join('|');
4449
+
4450
+ if (transformKey !== this._lastWebglCanvasMotionTransformKey) {
4451
+ this._lastWebglCanvasMotionTransformKey = transformKey;
4452
+ element.style.transformOrigin = '50% 50%';
4453
+ element.style.willChange = 'transform';
4454
+ element.style.transform = `translate3d(${translateX.toFixed(3)}px, ${translateY.toFixed(3)}px, 0) scale(${scale.toFixed(5)})`;
4455
+ }
4456
+
4457
+ this._webglCanvasMotionTransformActive = true;
4458
+ return true;
4459
+ }
4460
+
3495
4461
  refreshAgentConsoleVisibilityTracking(force = false) {
3496
4462
  if (!force && this._agentConsoleVisibilityTrackingDirty !== true) {
3497
4463
  return;
@@ -3884,8 +4850,11 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
3884
4850
  viewHeight = 2 * Math.tan(vfov / 2) * Math.max(1, cameraZ);
3885
4851
  viewWidth = viewHeight * Math.max(0.1, Number(camera.aspect || 1));
3886
4852
 
3887
- const worldPerPxX = viewWidth / Math.max(1, this.container.clientWidth);
3888
- const worldPerPxY = viewHeight / Math.max(1, this.container.clientHeight);
4853
+ const viewportMetrics = getCachedViewportMetrics(this);
4854
+ const viewportWidth = Math.max(1, Number(viewportMetrics.width || 1));
4855
+ const viewportHeight = Math.max(1, Number(viewportMetrics.height || 1));
4856
+ const worldPerPxX = viewWidth / viewportWidth;
4857
+ const worldPerPxY = viewHeight / viewportHeight;
3889
4858
  const rawMarginWorld = cullingMarginPx * Math.max(worldPerPxX, worldPerPxY);
3890
4859
  const cellSize = this.GRID_CELL_SIZE || 500;
3891
4860
  marginWorld = Math.min(rawMarginWorld, cellSize * maxMarginCells);
@@ -4465,7 +5434,7 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
4465
5434
 
4466
5435
  if (this.isZooming && !isCameraMoving && timeSinceLastInput > ZOOM_END_DELAY_MS) {
4467
5436
  this.isZooming = false;
4468
- document.body.classList.remove('is-zooming');
5437
+ document.body?.classList?.remove?.('is-zooming');
4469
5438
  this.cssRenderer?.domElement?.classList?.remove?.('is-zooming');
4470
5439
  if (FRAME_PERF_DEBUG) {
4471
5440
  console.log(`[Perf] Zoom ended (${timeSinceLastInput.toFixed(0)}ms since last input)`);
@@ -4476,10 +5445,12 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
4476
5445
  ? requestIdleCallback : (cb) => setTimeout(cb, 0);
4477
5446
  scheduleWillChangeOff(() => {
4478
5447
  // 패닝/줌이 다시 시작됐으면 건너뜀
4479
- if (!moduleRef.isZooming && !moduleRef.isPanning) {
4480
- if (window.MindMapCss3DManager?.setWillChange) {
4481
- window.MindMapCss3DManager.setWillChange(false, moduleRef);
4482
- }
5448
+ if (!moduleRef.isZooming &&
5449
+ !moduleRef.isPanning &&
5450
+ moduleRef.renderDebugFlags?.enableCss3dWillChange === true) {
5451
+ if (window.MindMapCss3DManager?.setWillChange) {
5452
+ window.MindMapCss3DManager.setWillChange(false, moduleRef);
5453
+ }
4483
5454
  }
4484
5455
  });
4485
5456
  // ▲▲▲ [Fix] ▲▲▲
@@ -4488,8 +5459,12 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
4488
5459
  _profileSection('state');
4489
5460
 
4490
5461
  if (isCameraMoving !== this.prevIsCameraMoving) {
4491
- // Update will-change state
4492
- if (!this.isZooming && !this.isPanning && window.MindMapCss3DManager?.setWillChange) {
5462
+ const shouldSyncCss3dWillChangeForCameraMotion =
5463
+ this.renderDebugFlags?.enableCss3dWillChange === true;
5464
+ if (!this.isZooming &&
5465
+ !this.isPanning &&
5466
+ shouldSyncCss3dWillChangeForCameraMotion &&
5467
+ window.MindMapCss3DManager?.setWillChange) {
4493
5468
  window.MindMapCss3DManager.setWillChange(isCameraMoving, this);
4494
5469
  }
4495
5470
 
@@ -4540,6 +5515,19 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
4540
5515
  this._overlayDirty = true;
4541
5516
  }
4542
5517
 
5518
+ if (this._webglCameraMotionDeferred === true ||
5519
+ this._themeCameraMotionDeferred === true ||
5520
+ this._css3dVisualDeferredForMotion === true) {
5521
+ this._forceUpdateFrames = Math.max(this._forceUpdateFrames || 0, 2);
5522
+ this._forceThemeAnimationFrame = true;
5523
+ if (this._css3dVisualDeferredForMotion === true) {
5524
+ this._css3dVisualChangedSinceLastRender = true;
5525
+ }
5526
+ this._webglCameraMotionDeferred = false;
5527
+ this._themeCameraMotionDeferred = false;
5528
+ this._css3dVisualDeferredForMotion = false;
5529
+ }
5530
+
4543
5531
  if (this.lodRenderer?.hasPendingLineDetailRestore?.(this.camera, this) === true) {
4544
5532
  this._forceUpdateFrames = Math.max(this._forceUpdateFrames || 0, 6);
4545
5533
  }
@@ -4567,8 +5555,51 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
4567
5555
  // 3. Unified Visibility & LOD Pass (One Loop to Rule Them All)
4568
5556
  // ----------------------------------------------------------------
4569
5557
  this.frameCount = (this.frameCount || 0) + 1;
4570
- const hasPendingNearCssWarmup = this.useLODRendering &&
4571
- this.lodRenderer?.hasPendingNearCssWarmup?.(this) === true;
5558
+ if (tryRunUltraEarlyCameraOnlyFrame(
5559
+ this,
5560
+ loop,
5561
+ frameStart,
5562
+ isCameraMoving,
5563
+ shouldCaptureFramePerf,
5564
+ _frameProfTimes,
5565
+ _profileSection
5566
+ ) === true) {
5567
+ return;
5568
+ }
5569
+ if (tryRunUltraEarlyResidentCameraOnlyFrame(
5570
+ this,
5571
+ loop,
5572
+ frameStart,
5573
+ isCameraMoving,
5574
+ shouldCaptureFramePerf,
5575
+ _frameProfTimes,
5576
+ _profileSection
5577
+ ) === true) {
5578
+ return;
5579
+ }
5580
+
5581
+ const warmupProbeForceUpdateFrames = Math.max(0, Number(this._forceUpdateFrames || 0));
5582
+ const warmupProbeCameraNavigationForceFrames = Math.max(0, Number(this._cameraNavigationForceFrames || 0));
5583
+ const warmupProbePanLiveRefreshForceFrames = Math.max(0, Number(this._panLiveRefreshForceFrames || 0));
5584
+ const warmupProbeCameraWakeFrames = Math.max(
5585
+ warmupProbeCameraNavigationForceFrames,
5586
+ warmupProbePanLiveRefreshForceFrames
5587
+ );
5588
+ const shouldDeferMotionWarmupAndResidentChecks =
5589
+ (this.isPanning === true || this.isZooming === true || isCameraMoving === true) &&
5590
+ this.isDraggingNode !== true &&
5591
+ this.isDraggingMultipleNodes !== true &&
5592
+ this.isResizing !== true &&
5593
+ this.isWindowResizing !== true &&
5594
+ this.isLoading !== true &&
5595
+ (
5596
+ warmupProbeForceUpdateFrames <= 0 ||
5597
+ warmupProbeCameraWakeFrames >= warmupProbeForceUpdateFrames
5598
+ );
5599
+ const hasPendingNearCssWarmup = shouldDeferMotionWarmupAndResidentChecks === true
5600
+ ? false
5601
+ : this.useLODRendering &&
5602
+ this.lodRenderer?.hasPendingNearCssWarmup?.(this) === true;
4572
5603
  const isResidentMidFarLodFrame = this.useLODRendering &&
4573
5604
  this.lodRenderer?.usesResidentMidFarInstances?.(this.camera) === true;
4574
5605
  const isFullResMode = this.lodRenderer?.isInLODMode !== true;
@@ -4622,9 +5653,15 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
4622
5653
  hasForcedUpdate &&
4623
5654
  hasOnlyCameraNavigationForcedUpdate !== true;
4624
5655
  const isLodModeActive = this.lodRenderer?.isInLODMode === true;
5656
+ if (shouldDeferMotionWarmupAndResidentChecks === true && isResidentMidFarLodFrame === true) {
5657
+ this._wasLodMotionFrozenLastFrame = true;
5658
+ this._preserveVisibleSetThisFrame = true;
5659
+ }
4625
5660
  const residentDirty = isResidentMidFarLodFrame &&
5661
+ shouldDeferMotionWarmupAndResidentChecks !== true &&
4626
5662
  this.lodRenderer?.hasResidentDirtyWork?.(this.camera, this) === true;
4627
5663
  const residentBandChanging = isResidentMidFarLodFrame &&
5664
+ shouldDeferMotionWarmupAndResidentChecks !== true &&
4628
5665
  this.lodRenderer?.willLodBandChange?.(this.camera) === true;
4629
5666
  let canDeferResidentDirtyIdleFrame = false;
4630
5667
  if (isResidentMidFarLodFrame &&
@@ -4698,31 +5735,41 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
4698
5735
  }
4699
5736
  const isInteractiveFrame = this.isZooming || this.isPanning || isCameraMoving || isNodeInteracting || !!this.isWindowResizing;
4700
5737
  const useTextOverlayV2 = !isBoardLoading && this.useTextOverlayV2 === true && !!window.MindMapTextOverlayV2;
5738
+ const isPassiveDomOverlayRuntimeEnabled =
5739
+ useTextOverlayV2 &&
5740
+ window.MindMapTextOverlayV2?.isPassiveDomOverlayEnabled?.() === true;
4701
5741
  const renderDebugFlags = activeRenderDebugFlags;
4702
5742
  const isCss3dEnabled = !isBoardLoading && renderDebugFlags.enableCss3d !== false;
4703
5743
 
4704
- const hasKnownTextOverlayFocus = !!(
5744
+ const hasSelectableTextOverlayFocus = !!(
4705
5745
  String(this._textOverlayV2State?.selectionNodeId || '').trim() ||
5746
+ String(this._selectableTextOverlayNodeId || '').trim()
5747
+ );
5748
+ const hasEditingTextOverlayFocus = !!(
4706
5749
  String(this._textOverlayV2State?.editing?.nodeId || '').trim() ||
4707
- String(this._selectableTextOverlayNodeId || '').trim() ||
4708
5750
  String(this._editingOverlayState?.nodeId || '').trim()
4709
5751
  );
4710
- const hasPendingCss3dTransformWork = !!(
5752
+ const hasPendingCss3dStructuralTransformWork = !!(
4711
5753
  (this._cssTransformDirtyIds?.size || 0) > 0 ||
4712
- this._cssParentRepairNeeded === true ||
5754
+ this._cssParentRepairNeeded === true
5755
+ );
5756
+ const hasPendingCss3dVisualWork = !!(
4713
5757
  this._css3dVisualChangedThisFrame === true ||
4714
5758
  this._css3dVisualChangedSinceLastRender === true
4715
5759
  );
4716
- const canUseUaImmediateCameraOnlyFrame = !!(
5760
+ const canUseUaImmediateCameraOnlyFrame = CAMERA_MOTION_VISUAL_ONLY_FAST_PATH_ENABLED === true && !!(
4717
5761
  shouldPreferCameraOnlyMotionPipeline === true &&
4718
5762
  isCss3dEnabled === true &&
4719
5763
  !!this.cssRenderer &&
4720
5764
  typeof this.cssRenderer.renderCamera === 'function' &&
5765
+ !!this.renderer &&
5766
+ !!this.scene &&
5767
+ !!this.camera &&
4721
5768
  isLodModeActive !== true &&
4722
5769
  isCameraMoving === true &&
4723
5770
  this.isSelecting !== true &&
4724
- !hasKnownTextOverlayFocus &&
4725
- !hasPendingCss3dTransformWork &&
5771
+ !hasEditingTextOverlayFocus &&
5772
+ !hasPendingCss3dStructuralTransformWork &&
4726
5773
  !isBoardLoading
4727
5774
  );
4728
5775
  if (canUseUaImmediateCameraOnlyFrame) {
@@ -4731,50 +5778,119 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
4731
5778
  this._wasLodMotionFrozenLastFrame = false;
4732
5779
  this._fullResVisibilityReconcilePending = true;
4733
5780
  this._preserveVisibleSetThisFrame = true;
4734
- this._lastVisibilityCullingSkipReason = 'ua-immediate-camera-only';
4735
- if (this._businessAutomationEdgeVisualLayer?.isConnected ||
4736
- this._businessAutomationEdgeLayer?.isConnected) {
4737
- this._businessAutomationEdgesDeferredForMotion = true;
4738
- }
5781
+ this._lastVisibilityCullingSkipReason = 'ua-visual-only-motion';
4739
5782
  if (this._overlayDirty === true) {
4740
5783
  this._deferPassiveOverlayRefresh = true;
4741
5784
  }
5785
+ if (hasSelectableTextOverlayFocus) {
5786
+ this._deferPassiveOverlayRefresh = true;
5787
+ }
5788
+ if (hasPendingCss3dVisualWork) {
5789
+ this._css3dVisualDeferredForMotion = true;
5790
+ }
4742
5791
 
4743
- const approxVisibleNodeCountImmediate = Number(this._visibleIds?.size || this._prevVisibleIds?.size || 0);
4744
- const totalNodeCountImmediate = Number(this.nodeObjectsById?.size || 0);
4745
- syncMindCanvasPerfClasses({
4746
- moving: true,
4747
- dense:
4748
- approxVisibleNodeCountImmediate >= DENSE_VISIBLE_NODE_THRESHOLD ||
4749
- totalNodeCountImmediate >= DENSE_TOTAL_NODE_THRESHOLD
4750
- });
5792
+ const needsImmediatePerfClassSync = shouldSyncMindCanvasPerfClasses();
5793
+ const approxVisibleNodeCountImmediate =
5794
+ needsImmediatePerfClassSync || shouldCaptureFramePerf
5795
+ ? Number(this._visibleIds?.size || this._prevVisibleIds?.size || 0)
5796
+ : 0;
5797
+ if (needsImmediatePerfClassSync) {
5798
+ const totalNodeCountImmediate = Number(this.nodeObjectsById?.size || 0);
5799
+ syncMindCanvasPerfClasses({
5800
+ moving: true,
5801
+ dense:
5802
+ approxVisibleNodeCountImmediate >= DENSE_VISIBLE_NODE_THRESHOLD ||
5803
+ totalNodeCountImmediate >= DENSE_TOTAL_NODE_THRESHOLD
5804
+ });
5805
+ }
4751
5806
 
4752
- if ((this.frameCount % CAMERA_MOTION_WEBGL_FRAME_INTERVAL) === 0) {
4753
- updateCursorDomElement(this);
4754
- if (renderDebugFlags.enableWebglRender !== false && this.renderer && this.scene && this.camera) {
5807
+ updateCursorDomElement(this);
5808
+ const canReuseWebglCanvasForImmediateMotion = !!(
5809
+ canUseWebglCanvasTransformForCameraMotion(this) === true &&
5810
+ renderDebugFlags.enableWebglRender !== false &&
5811
+ this._lastWebglRenderedCameraState &&
5812
+ this.renderer?.domElement
5813
+ );
5814
+ if (canReuseWebglCanvasForImmediateMotion === true) {
5815
+ deferContinuousThemeForCameraMotion(this);
5816
+ }
5817
+ if (!canReuseWebglCanvasForImmediateMotion &&
5818
+ renderDebugFlags.enableThemeAnimation !== false &&
5819
+ this.currentThemeObject &&
5820
+ typeof this.currentThemeObject.animate === 'function') {
5821
+ const themeRunner = window.MindMapThemes?.runAnimationIfNeeded;
5822
+ if (typeof themeRunner === 'function') {
5823
+ themeRunner(this.currentThemeName, this.currentThemeObject, this.camera, {
5824
+ force: this._forceThemeAnimationFrame === true,
5825
+ isCameraMoving: true,
5826
+ isPanning: this.isPanning === true,
5827
+ isZooming: this.isZooming === true,
5828
+ isResizing: this.isWindowResizing === true,
5829
+ isInteractiveFrame: true
5830
+ });
5831
+ } else {
5832
+ this.currentThemeObject.animate(this.camera);
5833
+ }
5834
+ this._forceThemeAnimationFrame = false;
5835
+ }
5836
+ let usedWebglCanvasCameraTransformImmediate = false;
5837
+ if (renderDebugFlags.enableWebglRender !== false) {
5838
+ if (canReuseWebglCanvasForImmediateMotion === true &&
5839
+ this._applyWebglCanvasCameraTransform() === true) {
5840
+ usedWebglCanvasCameraTransformImmediate = true;
5841
+ this._webglCameraMotionDeferred = true;
5842
+ this._lastWebglRenderInfo = {
5843
+ calls: 0,
5844
+ triangles: 0,
5845
+ points: 0,
5846
+ lines: 0,
5847
+ skipped: true,
5848
+ webglCanvasCameraTransform: true,
5849
+ uaVisualOnlyMotion: true
5850
+ };
5851
+ } else {
5852
+ this._clearWebglCanvasCameraTransform();
4755
5853
  this.renderer.render(this.scene, this.camera);
5854
+ this._recordWebglRenderedCameraState();
4756
5855
  const renderInfo = this.renderer?.info?.render || null;
4757
5856
  this._lastWebglRenderInfo = renderInfo ? {
4758
5857
  calls: Number(renderInfo.calls || 0),
4759
5858
  triangles: Number(renderInfo.triangles || 0),
4760
5859
  points: Number(renderInfo.points || 0),
4761
5860
  lines: Number(renderInfo.lines || 0),
4762
- uaImmediateCameraOnly: true
5861
+ uaVisualOnlyMotion: true,
5862
+ webglCanvasCameraBootstrap: true
4763
5863
  } : null;
4764
5864
  }
4765
- } else if (renderDebugFlags.enableWebglRender !== false) {
4766
- this._lastWebglRenderInfo = {
4767
- calls: 0,
4768
- triangles: 0,
4769
- points: 0,
4770
- lines: 0,
4771
- skipped: true,
4772
- uaImmediateCameraOnly: true
4773
- };
4774
5865
  }
5866
+ this._syncMotionGridLayerForCameraNavigation(usedWebglCanvasCameraTransformImmediate);
4775
5867
 
4776
5868
  this.cssRenderer.renderCamera(this.camera);
4777
- _profileSection('uaImmediateCameraOnly');
5869
+ const shouldSyncPassiveOverlayTransformDuringMotion =
5870
+ window.MindMapTextOverlayV2?.isPassiveDomOverlayEnabled?.() === true &&
5871
+ this.useTextOverlayV2 === true &&
5872
+ this.renderDebugFlags?.enableDomOverlay !== false;
5873
+ if (shouldSyncPassiveOverlayTransformDuringMotion &&
5874
+ window.MindMapTextOverlayV2?.syncPassiveViewTransform) {
5875
+ window.MindMapTextOverlayV2.syncPassiveViewTransform(this);
5876
+ }
5877
+ if ((this._businessAutomationEdgeVisualLayer?.isConnected ||
5878
+ this._businessAutomationEdgeLayer?.isConnected) &&
5879
+ window.MindMapCss3DManager?.renderBusinessAutomationEdges) {
5880
+ const hasBusinessAutomationConnectionDraft =
5881
+ window.MindMapCss3DManager?.hasBusinessAutomationConnectionDraft?.() === true;
5882
+ const transformedBusinessAutomationEdges =
5883
+ window.MindMapCss3DManager.syncBusinessAutomationEdgesForCameraMotion?.(this) === true;
5884
+ if (transformedBusinessAutomationEdges) {
5885
+ this._businessAutomationEdgesDeferredForMotion = true;
5886
+ } else if (hasBusinessAutomationConnectionDraft) {
5887
+ window.MindMapCss3DManager.renderBusinessAutomationEdges(this);
5888
+ this._businessAutomationEdgesDeferredForMotion = false;
5889
+ } else {
5890
+ this._businessAutomationEdgesDeferredForMotion = true;
5891
+ }
5892
+ }
5893
+ _profileSection('uaVisualOnlyMotion');
4778
5894
 
4779
5895
  const frameEnd = performance.now();
4780
5896
  const frameDuration = frameEnd - frameStart;
@@ -4803,22 +5919,22 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
4803
5919
  residentMidFarStable: false,
4804
5920
  residentDirty: false,
4805
5921
  residentBandChanging: false,
4806
- viewSyncMode: 'ua-immediate-camera-only',
5922
+ viewSyncMode: 'ua-visual-only-motion',
4807
5923
  lodUpdateCalled: false,
4808
5924
  lodUpdateSkipped: true,
4809
- lodUpdateSkipReason: 'ua-immediate-camera-only',
5925
+ lodUpdateSkipReason: 'ua-visual-only-motion',
4810
5926
  lodSkipReason: '',
4811
5927
  lodDirtyReason: '',
4812
5928
  lodDirtyState: null,
4813
5929
  viewSyncBreakdown: null,
4814
5930
  shouldRunVisibilityCulling: false,
4815
5931
  visibilityStableSkip: true,
4816
- visibilitySkipReason: 'ua-immediate-camera-only',
5932
+ visibilitySkipReason: 'ua-visual-only-motion',
4817
5933
  incrementalPanVisibility: false,
4818
5934
  shouldSyncCss3dTransforms: false,
4819
5935
  shouldRenderCss3d: false,
4820
5936
  usedCss3dCameraFastPath: true,
4821
- uaImmediateCameraOnly: true,
5937
+ uaVisualOnlyMotion: true,
4822
5938
  shouldSyncTextOverlays: false,
4823
5939
  usedPassiveOverlayCameraFastPath: false,
4824
5940
  passiveOverlaySuppressed: this._suppressPassiveDomOverlayDuringZoom === true,
@@ -4841,24 +5957,388 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
4841
5957
  const isLodUpdateEnabled = renderDebugFlags.enableLodUpdate !== false;
4842
5958
  const isWebglRenderEnabled = !isBoardLoading && renderDebugFlags.enableWebglRender !== false;
4843
5959
  const isThemeAnimationEnabled = !isBoardLoading && renderDebugFlags.enableThemeAnimation !== false;
4844
- const themeAnimationMode =
4845
- window.MindMapThemes?.getAnimationMode?.(this.currentThemeName, this.currentThemeObject) ||
4846
- 'none';
4847
- const isContinuousTheme = themeAnimationMode === 'continuous';
4848
5960
  const isCameraNavigationOnlyInteractiveFrame =
4849
5961
  (this.isPanning === true || this.isZooming === true || isCameraMoving === true) &&
4850
5962
  !isNodeInteracting &&
4851
5963
  !this.isWindowResizing &&
4852
5964
  !hasNonCameraNavigationForcedUpdate;
5965
+ const canUseUaStationaryCameraNavigationAnyModeFrame = CAMERA_MOTION_VISUAL_ONLY_FAST_PATH_ENABLED === true && !!(
5966
+ isCameraNavigationOnlyInteractiveFrame === true &&
5967
+ isCameraMoving !== true &&
5968
+ (this.isPanning === true || this.isZooming === true) &&
5969
+ shouldUpdate !== true &&
5970
+ this.isSelecting !== true &&
5971
+ !hasEditingTextOverlayFocus &&
5972
+ !hasPendingCss3dStructuralTransformWork &&
5973
+ !hasPendingCss3dVisualWork &&
5974
+ !hasPendingNearCssWarmup &&
5975
+ !isBoardLoading &&
5976
+ this.isWindowResizing !== true &&
5977
+ (isWebglRenderEnabled !== true || !!this._lastWebglRenderedCameraState)
5978
+ );
5979
+ if (canUseUaStationaryCameraNavigationAnyModeFrame) {
5980
+ this._preserveVisibleSetThisFrame = true;
5981
+ this._lastVisibilityCullingSkipReason = 'ua-stationary-visual-motion';
5982
+ if (this._overlayDirty === true || hasSelectableTextOverlayFocus) {
5983
+ this._deferPassiveOverlayRefresh = true;
5984
+ }
5985
+
5986
+ updateCursorDomElement(this);
5987
+ const canReuseWebglCanvasForStationaryMotion = !!(
5988
+ canUseWebglCanvasTransformForCameraMotion(this) === true &&
5989
+ isWebglRenderEnabled &&
5990
+ this._lastWebglRenderedCameraState &&
5991
+ this.renderer?.domElement
5992
+ );
5993
+ if (canReuseWebglCanvasForStationaryMotion === true) {
5994
+ deferContinuousThemeForCameraMotion(this);
5995
+ }
5996
+ if (!canReuseWebglCanvasForStationaryMotion &&
5997
+ renderDebugFlags.enableThemeAnimation !== false &&
5998
+ this.currentThemeObject &&
5999
+ typeof this.currentThemeObject.animate === 'function') {
6000
+ const themeRunner = window.MindMapThemes?.runAnimationIfNeeded;
6001
+ if (typeof themeRunner === 'function') {
6002
+ themeRunner(this.currentThemeName, this.currentThemeObject, this.camera, {
6003
+ force: this._forceThemeAnimationFrame === true,
6004
+ isCameraMoving: false,
6005
+ isPanning: this.isPanning === true,
6006
+ isZooming: this.isZooming === true,
6007
+ isResizing: this.isWindowResizing === true,
6008
+ isInteractiveFrame: true
6009
+ });
6010
+ } else {
6011
+ this.currentThemeObject.animate(this.camera);
6012
+ }
6013
+ this._forceThemeAnimationFrame = false;
6014
+ }
6015
+ let usedWebglCanvasCameraTransformStationary = false;
6016
+ if (isWebglRenderEnabled && this.renderer && this.scene && this.camera) {
6017
+ if (canReuseWebglCanvasForStationaryMotion === true &&
6018
+ this._applyWebglCanvasCameraTransform() === true) {
6019
+ usedWebglCanvasCameraTransformStationary = true;
6020
+ this._webglCameraMotionDeferred = true;
6021
+ this._lastWebglRenderInfo = {
6022
+ calls: 0,
6023
+ triangles: 0,
6024
+ points: 0,
6025
+ lines: 0,
6026
+ skipped: true,
6027
+ webglCanvasCameraTransform: true,
6028
+ uaStationaryVisualMotion: true
6029
+ };
6030
+ } else {
6031
+ this._clearWebglCanvasCameraTransform();
6032
+ this.renderer.render(this.scene, this.camera);
6033
+ this._recordWebglRenderedCameraState();
6034
+ const renderInfo = this.renderer?.info?.render || null;
6035
+ this._lastWebglRenderInfo = renderInfo ? {
6036
+ calls: Number(renderInfo.calls || 0),
6037
+ triangles: Number(renderInfo.triangles || 0),
6038
+ points: Number(renderInfo.points || 0),
6039
+ lines: Number(renderInfo.lines || 0),
6040
+ uaStationaryVisualMotion: true,
6041
+ webglCanvasCameraBootstrap: true
6042
+ } : null;
6043
+ }
6044
+ }
6045
+ this._syncMotionGridLayerForCameraNavigation(usedWebglCanvasCameraTransformStationary);
6046
+ if (this.cssRenderer && typeof this.cssRenderer.renderCamera === 'function') {
6047
+ this.cssRenderer.renderCamera(this.camera);
6048
+ }
6049
+ if ((this._businessAutomationEdgeVisualLayer?.isConnected ||
6050
+ this._businessAutomationEdgeLayer?.isConnected) &&
6051
+ window.MindMapCss3DManager?.renderBusinessAutomationEdges) {
6052
+ const hasBusinessAutomationConnectionDraft =
6053
+ window.MindMapCss3DManager?.hasBusinessAutomationConnectionDraft?.() === true;
6054
+ const transformedBusinessAutomationEdges =
6055
+ window.MindMapCss3DManager.syncBusinessAutomationEdgesForCameraMotion?.(this) === true;
6056
+ if (transformedBusinessAutomationEdges) {
6057
+ this._businessAutomationEdgesDeferredForMotion = true;
6058
+ } else if (hasBusinessAutomationConnectionDraft) {
6059
+ window.MindMapCss3DManager.renderBusinessAutomationEdges(this);
6060
+ this._businessAutomationEdgesDeferredForMotion = false;
6061
+ } else {
6062
+ this._businessAutomationEdgesDeferredForMotion = true;
6063
+ }
6064
+ }
6065
+
6066
+ _profileSection('uaStationaryVisualMotion');
6067
+
6068
+ const frameEnd = performance.now();
6069
+ const frameDuration = frameEnd - frameStart;
6070
+ this._lastFrameDurationMs = frameDuration;
6071
+ this._lastFrameMetricsAt = frameEnd;
6072
+ if (shouldCaptureFramePerf) {
6073
+ const sectionTimes = {};
6074
+ Object.entries(_frameProfTimes)
6075
+ .filter(([key, value]) => key !== 'visibleNodes' && typeof value === 'number')
6076
+ .forEach(([key, value]) => {
6077
+ sectionTimes[key] = Number(value.toFixed(2));
6078
+ });
6079
+ this._lastFramePerfProfile = {
6080
+ durationMs: Number(frameDuration.toFixed(2)),
6081
+ update: false,
6082
+ cameraZ: Number((this.camera?.position?.z || 0).toFixed(2)),
6083
+ isZooming: this.isZooming === true,
6084
+ isPanning: this.isPanning === true,
6085
+ isCameraMoving: false,
6086
+ isInLodMode: this.lodRenderer?.isInLODMode === true,
6087
+ lodBand: this.lodRenderer?._currentLodBand || null,
6088
+ visibleNodes: Number(this._visibleIds?.size || this._prevVisibleIds?.size || 0),
6089
+ freezeVisibility: true,
6090
+ freezeLod: true,
6091
+ freezeFullRes: true,
6092
+ residentMidFarStable: residentMidFarStable === true,
6093
+ residentDirty: residentDirty === true,
6094
+ residentBandChanging: residentBandChanging === true,
6095
+ viewSyncMode: 'ua-stationary-visual-motion',
6096
+ lodUpdateCalled: false,
6097
+ lodUpdateSkipped: true,
6098
+ lodUpdateSkipReason: 'ua-stationary-visual-motion',
6099
+ lodSkipReason: '',
6100
+ lodDirtyReason: '',
6101
+ lodDirtyState: null,
6102
+ viewSyncBreakdown: null,
6103
+ shouldRunVisibilityCulling: false,
6104
+ visibilityStableSkip: true,
6105
+ visibilitySkipReason: 'ua-stationary-visual-motion',
6106
+ incrementalPanVisibility: false,
6107
+ shouldSyncCss3dTransforms: false,
6108
+ shouldRenderCss3d: false,
6109
+ usedCss3dCameraFastPath: false,
6110
+ uaStationaryVisualMotion: true,
6111
+ shouldSyncTextOverlays: false,
6112
+ usedPassiveOverlayCameraFastPath: false,
6113
+ passiveOverlaySuppressed: this._suppressPassiveDomOverlayDuringZoom === true,
6114
+ webgl: this._lastWebglRenderInfo || null,
6115
+ sections: sectionTimes
6116
+ };
6117
+ updateFramePerfPeakProfile(this, this._lastFramePerfProfile, frameEnd);
6118
+ } else if (this._lastFramePerfProfile) {
6119
+ this._lastFramePerfProfile = null;
6120
+ }
6121
+
6122
+ this._lastAnimationLoopDelayMs = 0;
6123
+ this._animationLoopExecuting = false;
6124
+ this._scheduleAnimationLoop(loop, 0);
6125
+ return;
6126
+ }
6127
+ const shouldRenderCss3dInLodForUaResidentCameraOnly = !!(
6128
+ isCss3dEnabled === true &&
6129
+ !!this.cssRenderer &&
6130
+ typeof this.cssRenderer.renderCamera === 'function' &&
6131
+ this.lodRenderer?.hasCss3dLodCandidates?.() === true
6132
+ );
6133
+ const canUseUaResidentCameraOnlyFrame = CAMERA_MOTION_VISUAL_ONLY_FAST_PATH_ENABLED === true && !!(
6134
+ shouldPreferCameraOnlyMotionPipeline === true &&
6135
+ isCameraNavigationOnlyInteractiveFrame === true &&
6136
+ isResidentMidFarLodFrame === true &&
6137
+ residentMidFarStable === true &&
6138
+ shouldUpdate !== true &&
6139
+ isCameraMoving === true &&
6140
+ this.isSelecting !== true &&
6141
+ !hasEditingTextOverlayFocus &&
6142
+ !hasPendingCss3dStructuralTransformWork &&
6143
+ !isBoardLoading &&
6144
+ this.isWindowResizing !== true &&
6145
+ (
6146
+ isWebglRenderEnabled === true ||
6147
+ shouldRenderCss3dInLodForUaResidentCameraOnly === true
6148
+ )
6149
+ );
6150
+ if (canUseUaResidentCameraOnlyFrame) {
6151
+ this._fullResMotionFrozen = false;
6152
+ this._wasViewStateMotionFrozenLastFrame = false;
6153
+ this._wasLodMotionFrozenLastFrame = true;
6154
+ this._preserveVisibleSetThisFrame = true;
6155
+ this._lastVisibilityCullingSkipReason = 'ua-resident-visual-only-motion';
6156
+ if (this._overlayDirty === true || hasSelectableTextOverlayFocus) {
6157
+ this._deferPassiveOverlayRefresh = true;
6158
+ }
6159
+ if (hasPendingCss3dVisualWork) {
6160
+ this._css3dVisualDeferredForMotion = true;
6161
+ }
6162
+
6163
+ const needsResidentPerfClassSync = shouldSyncMindCanvasPerfClasses();
6164
+ const approxVisibleNodeCountResident =
6165
+ needsResidentPerfClassSync || shouldCaptureFramePerf
6166
+ ? Number(this._visibleIds?.size || this._prevVisibleIds?.size || 0)
6167
+ : 0;
6168
+ if (needsResidentPerfClassSync) {
6169
+ const totalNodeCountResident = Number(this.nodeObjectsById?.size || 0);
6170
+ syncMindCanvasPerfClasses({
6171
+ moving: true,
6172
+ dense:
6173
+ approxVisibleNodeCountResident >= DENSE_VISIBLE_NODE_THRESHOLD ||
6174
+ totalNodeCountResident >= DENSE_TOTAL_NODE_THRESHOLD
6175
+ });
6176
+ }
6177
+
6178
+ updateCursorDomElement(this);
6179
+ const canReuseWebglCanvasForResidentMotion = !!(
6180
+ canUseWebglCanvasTransformForCameraMotion(this) === true &&
6181
+ isWebglRenderEnabled === true &&
6182
+ this._lastWebglRenderedCameraState &&
6183
+ this.renderer?.domElement
6184
+ );
6185
+ if (canReuseWebglCanvasForResidentMotion === true) {
6186
+ deferContinuousThemeForCameraMotion(this);
6187
+ }
6188
+ if (!canReuseWebglCanvasForResidentMotion &&
6189
+ renderDebugFlags.enableThemeAnimation !== false &&
6190
+ this.currentThemeObject &&
6191
+ typeof this.currentThemeObject.animate === 'function') {
6192
+ const themeRunner = window.MindMapThemes?.runAnimationIfNeeded;
6193
+ if (typeof themeRunner === 'function') {
6194
+ themeRunner(this.currentThemeName, this.currentThemeObject, this.camera, {
6195
+ force: this._forceThemeAnimationFrame === true,
6196
+ isCameraMoving: true,
6197
+ isPanning: this.isPanning === true,
6198
+ isZooming: this.isZooming === true,
6199
+ isResizing: this.isWindowResizing === true,
6200
+ isInteractiveFrame: true
6201
+ });
6202
+ } else {
6203
+ this.currentThemeObject.animate(this.camera);
6204
+ }
6205
+ this._forceThemeAnimationFrame = false;
6206
+ }
6207
+ if (isWebglRenderEnabled === true) {
6208
+ if (this.renderer && this.scene && this.camera) {
6209
+ if (canReuseWebglCanvasForResidentMotion === true &&
6210
+ this._applyWebglCanvasCameraTransform() === true) {
6211
+ this._webglCameraMotionDeferred = true;
6212
+ this._lastWebglRenderInfo = {
6213
+ calls: 0,
6214
+ triangles: 0,
6215
+ points: 0,
6216
+ lines: 0,
6217
+ skipped: true,
6218
+ webglCanvasCameraTransform: true,
6219
+ uaResidentVisualOnlyMotion: true
6220
+ };
6221
+ } else {
6222
+ this._clearWebglCanvasCameraTransform();
6223
+ this.renderer.render(this.scene, this.camera);
6224
+ this._recordWebglRenderedCameraState();
6225
+ const renderInfo = this.renderer?.info?.render || null;
6226
+ this._lastWebglRenderInfo = renderInfo ? {
6227
+ calls: Number(renderInfo.calls || 0),
6228
+ triangles: Number(renderInfo.triangles || 0),
6229
+ points: Number(renderInfo.points || 0),
6230
+ lines: Number(renderInfo.lines || 0),
6231
+ uaResidentVisualOnlyMotion: true,
6232
+ webglCanvasCameraBootstrap: true
6233
+ } : null;
6234
+ }
6235
+ }
6236
+ }
6237
+
6238
+ if (shouldRenderCss3dInLodForUaResidentCameraOnly === true) {
6239
+ this.cssRenderer.renderCamera(this.camera);
6240
+ }
6241
+ if ((this._businessAutomationEdgeVisualLayer?.isConnected ||
6242
+ this._businessAutomationEdgeLayer?.isConnected) &&
6243
+ window.MindMapCss3DManager?.renderBusinessAutomationEdges) {
6244
+ const hasBusinessAutomationConnectionDraft =
6245
+ window.MindMapCss3DManager?.hasBusinessAutomationConnectionDraft?.() === true;
6246
+ const transformedBusinessAutomationEdges =
6247
+ window.MindMapCss3DManager.syncBusinessAutomationEdgesForCameraMotion?.(this) === true;
6248
+ if (transformedBusinessAutomationEdges) {
6249
+ this._businessAutomationEdgesDeferredForMotion = true;
6250
+ } else if (hasBusinessAutomationConnectionDraft) {
6251
+ window.MindMapCss3DManager.renderBusinessAutomationEdges(this);
6252
+ this._businessAutomationEdgesDeferredForMotion = false;
6253
+ } else {
6254
+ this._businessAutomationEdgesDeferredForMotion = true;
6255
+ }
6256
+ }
6257
+ _profileSection('uaResidentVisualOnlyMotion');
6258
+
6259
+ const frameEnd = performance.now();
6260
+ const frameDuration = frameEnd - frameStart;
6261
+ this._lastFrameDurationMs = frameDuration;
6262
+ this._lastFrameMetricsAt = frameEnd;
6263
+ if (shouldCaptureFramePerf) {
6264
+ const sectionTimes = {};
6265
+ Object.entries(_frameProfTimes)
6266
+ .filter(([key, value]) => key !== 'visibleNodes' && typeof value === 'number')
6267
+ .forEach(([key, value]) => {
6268
+ sectionTimes[key] = Number(value.toFixed(2));
6269
+ });
6270
+ this._lastFramePerfProfile = {
6271
+ durationMs: Number(frameDuration.toFixed(2)),
6272
+ update: false,
6273
+ cameraZ: Number((this.camera?.position?.z || 0).toFixed(2)),
6274
+ isZooming: this.isZooming === true,
6275
+ isPanning: this.isPanning === true,
6276
+ isCameraMoving: true,
6277
+ isInLodMode: true,
6278
+ lodBand: this.lodRenderer?._currentLodBand || null,
6279
+ visibleNodes: approxVisibleNodeCountResident,
6280
+ freezeVisibility: true,
6281
+ freezeLod: true,
6282
+ freezeFullRes: false,
6283
+ residentMidFarStable: true,
6284
+ residentDirty: residentDirty === true,
6285
+ residentBandChanging: false,
6286
+ viewSyncMode: 'ua-resident-visual-only-motion',
6287
+ lodUpdateCalled: false,
6288
+ lodUpdateSkipped: true,
6289
+ lodUpdateSkipReason: 'ua-resident-visual-only-motion',
6290
+ lodSkipReason: '',
6291
+ lodDirtyReason: '',
6292
+ lodDirtyState: null,
6293
+ viewSyncBreakdown: null,
6294
+ shouldRunVisibilityCulling: false,
6295
+ visibilityStableSkip: true,
6296
+ visibilitySkipReason: 'ua-resident-visual-only-motion',
6297
+ incrementalPanVisibility: false,
6298
+ shouldSyncCss3dTransforms: false,
6299
+ shouldRenderCss3d: false,
6300
+ usedCss3dCameraFastPath: shouldRenderCss3dInLodForUaResidentCameraOnly === true,
6301
+ uaResidentVisualOnlyMotion: true,
6302
+ shouldSyncTextOverlays: false,
6303
+ usedPassiveOverlayCameraFastPath: false,
6304
+ passiveOverlaySuppressed: this._suppressPassiveDomOverlayDuringZoom === true,
6305
+ webgl: this._lastWebglRenderInfo || null,
6306
+ sections: sectionTimes
6307
+ };
6308
+ updateFramePerfPeakProfile(this, this._lastFramePerfProfile, frameEnd);
6309
+ } else if (this._lastFramePerfProfile) {
6310
+ this._lastFramePerfProfile = null;
6311
+ }
6312
+
6313
+ this._lastAnimationLoopDelayMs = 0;
6314
+ this._animationLoopExecuting = false;
6315
+ this._scheduleAnimationLoop(loop, 0);
6316
+ return;
6317
+ }
6318
+ const themeAnimationMode =
6319
+ window.MindMapThemes?.getAnimationMode?.(this.currentThemeName, this.currentThemeObject) ||
6320
+ 'none';
6321
+ const isContinuousTheme = themeAnimationMode === 'continuous';
6322
+ const shouldPauseThemeForCameraNavigation =
6323
+ isContinuousTheme === true &&
6324
+ isCameraNavigationOnlyInteractiveFrame === true &&
6325
+ this._forceThemeAnimationFrame !== true &&
6326
+ canUseWebglCanvasTransformForCameraMotion(this) === true;
4853
6327
  const allowContinuousThemeAnimationThisFrame =
4854
- isContinuousTheme !== true ||
4855
- (isInteractiveFrame === true && !isCameraNavigationOnlyInteractiveFrame) ||
4856
- (shouldUpdate === true && !isCameraNavigationOnlyInteractiveFrame) ||
4857
- hasNonCameraNavigationForcedUpdate === true ||
4858
- isBoardLoading === true ||
4859
- shouldUseAnimation60Fps === true ||
4860
- shouldUseAnimationLowFpsFrame === true ||
4861
- this._forceThemeAnimationFrame === true;
6328
+ shouldPauseThemeForCameraNavigation === true
6329
+ ? false
6330
+ : (isContinuousTheme !== true ||
6331
+ (isContinuousTheme === true && isCameraNavigationOnlyInteractiveFrame === true) ||
6332
+ (isInteractiveFrame === true && !isCameraNavigationOnlyInteractiveFrame) ||
6333
+ (shouldUpdate === true && !isCameraNavigationOnlyInteractiveFrame) ||
6334
+ hasNonCameraNavigationForcedUpdate === true ||
6335
+ isBoardLoading === true ||
6336
+ shouldUseAnimation60Fps === true ||
6337
+ shouldUseAnimationLowFpsFrame === true ||
6338
+ this._forceThemeAnimationFrame === true);
6339
+ if (shouldPauseThemeForCameraNavigation === true) {
6340
+ deferContinuousThemeForCameraMotion(this);
6341
+ }
4862
6342
  const isMenuOverlayUiEnabled = !isBoardLoading && renderDebugFlags.enableMenuOverlayUi !== false;
4863
6343
  const isMultiSelectOverlayUiEnabled = !isBoardLoading && renderDebugFlags.enableMultiSelectOverlayUi !== false;
4864
6344
  _profileSection('pipelineScheduling');
@@ -4886,17 +6366,15 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
4886
6366
  shouldPreferCameraOnlyMotionPipeline === true &&
4887
6367
  isCss3dEnabled === true &&
4888
6368
  !!this.cssRenderer &&
4889
- !hasFocusedTextOverlay &&
4890
6369
  !hasEditingOverlay;
4891
- const canUseUaEarlyCameraOnlyFrame = !!(
6370
+ const canUseUaEarlyCameraOnlyFrame = CAMERA_MOTION_PARTIAL_FRAME_SKIP_ENABLED === true && !!(
4892
6371
  isUaStyleCameraTransformFrame === true &&
4893
6372
  isFullResMode === true &&
4894
6373
  isResidentMidFarLodFrame !== true &&
4895
6374
  isCameraMoving === true &&
4896
6375
  this.isSelecting !== true &&
4897
6376
  typeof this.cssRenderer.renderCamera === 'function' &&
4898
- this._css3dVisualChangedThisFrame !== true &&
4899
- this._css3dVisualChangedSinceLastRender !== true
6377
+ !hasPendingCss3dStructuralTransformWork
4900
6378
  );
4901
6379
  if (canUseUaEarlyCameraOnlyFrame) {
4902
6380
  this._fullResMotionFrozen = true;
@@ -4909,20 +6387,35 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
4909
6387
  this._businessAutomationEdgeLayer?.isConnected) {
4910
6388
  this._businessAutomationEdgesDeferredForMotion = true;
4911
6389
  }
6390
+ if (hasPendingCss3dVisualWork) {
6391
+ this._css3dVisualDeferredForMotion = true;
6392
+ }
4912
6393
 
4913
- const approxVisibleNodeCountEarly = Number(this._visibleIds?.size || this._prevVisibleIds?.size || 0);
4914
- const totalNodeCountEarly = Number(this.nodeObjectsById?.size || 0);
4915
- syncMindCanvasPerfClasses({
4916
- moving: true,
4917
- dense:
4918
- approxVisibleNodeCountEarly >= DENSE_VISIBLE_NODE_THRESHOLD ||
4919
- totalNodeCountEarly >= DENSE_TOTAL_NODE_THRESHOLD
4920
- });
6394
+ const needsEarlyPerfClassSync = shouldSyncMindCanvasPerfClasses();
6395
+ const approxVisibleNodeCountEarly =
6396
+ needsEarlyPerfClassSync || shouldCaptureFramePerf
6397
+ ? Number(this._visibleIds?.size || this._prevVisibleIds?.size || 0)
6398
+ : 0;
6399
+ if (needsEarlyPerfClassSync) {
6400
+ const totalNodeCountEarly = Number(this.nodeObjectsById?.size || 0);
6401
+ syncMindCanvasPerfClasses({
6402
+ moving: true,
6403
+ dense:
6404
+ approxVisibleNodeCountEarly >= DENSE_VISIBLE_NODE_THRESHOLD ||
6405
+ totalNodeCountEarly >= DENSE_TOTAL_NODE_THRESHOLD
6406
+ });
6407
+ }
4921
6408
 
4922
- if ((this.frameCount % CAMERA_MOTION_WEBGL_FRAME_INTERVAL) === 0) {
6409
+ const shouldSampleWebglDuringCameraMotion =
6410
+ renderDebugFlags.sampleWebglDuringCameraMotion === true &&
6411
+ (this.frameCount % CAMERA_MOTION_WEBGL_FRAME_INTERVAL) === 0;
6412
+ let usedWebglCanvasCameraTransformEarly = false;
6413
+ if (shouldSampleWebglDuringCameraMotion) {
4923
6414
  updateCursorDomElement(this);
4924
6415
  if (isWebglRenderEnabled && this.renderer && this.scene && this.camera) {
6416
+ this._clearWebglCanvasCameraTransform();
4925
6417
  this.renderer.render(this.scene, this.camera);
6418
+ this._recordWebglRenderedCameraState();
4926
6419
  const renderInfo = this.renderer?.info?.render || null;
4927
6420
  this._lastWebglRenderInfo = renderInfo ? {
4928
6421
  calls: Number(renderInfo.calls || 0),
@@ -4932,17 +6425,62 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
4932
6425
  uaEarlyCameraOnly: true
4933
6426
  } : null;
4934
6427
  }
4935
- } else if (isWebglRenderEnabled) {
4936
- this._lastWebglRenderInfo = {
4937
- calls: 0,
4938
- triangles: 0,
4939
- points: 0,
4940
- lines: 0,
4941
- skipped: true,
4942
- uaEarlyCameraOnly: true
4943
- };
6428
+ } else if (isWebglRenderEnabled &&
6429
+ canUseWebglCanvasTransformForCameraMotion(this) === true) {
6430
+ usedWebglCanvasCameraTransformEarly =
6431
+ this._applyWebglCanvasCameraTransform() === true;
6432
+ this._webglCameraMotionDeferred = true;
6433
+ if (usedWebglCanvasCameraTransformEarly === true) {
6434
+ deferContinuousThemeForCameraMotion(this);
6435
+ this._lastWebglRenderInfo = {
6436
+ calls: 0,
6437
+ triangles: 0,
6438
+ points: 0,
6439
+ lines: 0,
6440
+ skipped: true,
6441
+ webglCanvasCameraTransform: true,
6442
+ uaEarlyCameraOnly: true
6443
+ };
6444
+ } else if (this.renderer && this.scene && this.camera) {
6445
+ let hiddenEarlyCameraMotionThemeObject = null;
6446
+ this._clearWebglCanvasCameraTransform();
6447
+ if (this._isMotionGridThemeEnabled() === true &&
6448
+ this.currentThemeObject &&
6449
+ this.currentThemeObject.visible !== false) {
6450
+ hiddenEarlyCameraMotionThemeObject = this.currentThemeObject;
6451
+ hiddenEarlyCameraMotionThemeObject.visible = false;
6452
+ }
6453
+ try {
6454
+ this.renderer.render(this.scene, this.camera);
6455
+ } finally {
6456
+ if (hiddenEarlyCameraMotionThemeObject) {
6457
+ hiddenEarlyCameraMotionThemeObject.visible = true;
6458
+ }
6459
+ }
6460
+ this._recordWebglRenderedCameraState();
6461
+ const renderInfo = this.renderer?.info?.render || null;
6462
+ this._lastWebglRenderInfo = renderInfo ? {
6463
+ calls: Number(renderInfo.calls || 0),
6464
+ triangles: Number(renderInfo.triangles || 0),
6465
+ points: Number(renderInfo.points || 0),
6466
+ lines: Number(renderInfo.lines || 0),
6467
+ backgroundThemeHidden: hiddenEarlyCameraMotionThemeObject ? true : undefined,
6468
+ uaEarlyCameraOnly: true,
6469
+ webglCanvasCameraBootstrap: true
6470
+ } : null;
6471
+ } else {
6472
+ this._lastWebglRenderInfo = {
6473
+ calls: 0,
6474
+ triangles: 0,
6475
+ points: 0,
6476
+ lines: 0,
6477
+ skipped: true,
6478
+ uaEarlyCameraOnly: true
6479
+ };
6480
+ }
4944
6481
  }
4945
6482
 
6483
+ this._syncMotionGridLayerForCameraNavigation(usedWebglCanvasCameraTransformEarly);
4946
6484
  this.cssRenderer.renderCamera(this.camera);
4947
6485
  _profileSection('uaEarlyCameraOnly');
4948
6486
 
@@ -5006,21 +6544,126 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5006
6544
  this._scheduleAnimationLoop(loop, 0);
5007
6545
  return;
5008
6546
  }
6547
+ const canUseUaStationaryCameraNavigationFrame = CAMERA_MOTION_PARTIAL_FRAME_SKIP_ENABLED === true && !!(
6548
+ isUaStyleCameraTransformFrame === true &&
6549
+ isFullResMode === true &&
6550
+ isResidentMidFarLodFrame !== true &&
6551
+ isCameraMoving !== true &&
6552
+ (this.isPanning === true || this.isZooming === true) &&
6553
+ this.isSelecting !== true &&
6554
+ !isNodeInteracting &&
6555
+ !hasNonCameraNavigationForcedUpdate &&
6556
+ !hasPendingNearCssWarmup &&
6557
+ !hasEditingOverlay &&
6558
+ !hasPendingCss3dStructuralTransformWork &&
6559
+ !hasPendingCss3dVisualWork &&
6560
+ shouldUpdate !== true &&
6561
+ !isBoardLoading &&
6562
+ this.isWindowResizing !== true
6563
+ );
6564
+ if (canUseUaStationaryCameraNavigationFrame) {
6565
+ this._fullResMotionFrozen = true;
6566
+ this._wasViewStateMotionFrozenLastFrame = true;
6567
+ this._wasLodMotionFrozenLastFrame = false;
6568
+ this._preserveVisibleSetThisFrame = true;
6569
+ this._lastVisibilityCullingSkipReason = 'ua-stationary-camera-navigation';
6570
+ if (this._businessAutomationEdgeVisualLayer?.isConnected ||
6571
+ this._businessAutomationEdgeLayer?.isConnected) {
6572
+ this._businessAutomationEdgesDeferredForMotion = true;
6573
+ }
6574
+ if (this._overlayDirty === true || hasFocusedTextOverlay) {
6575
+ this._deferPassiveOverlayRefresh = true;
6576
+ }
6577
+ if (isWebglRenderEnabled) {
6578
+ this._lastWebglRenderInfo = {
6579
+ calls: 0,
6580
+ triangles: 0,
6581
+ points: 0,
6582
+ lines: 0,
6583
+ skipped: true,
6584
+ uaStationaryCameraNavigation: true
6585
+ };
6586
+ }
6587
+
6588
+ _profileSection('uaStationaryCameraNavigation');
6589
+
6590
+ const frameEnd = performance.now();
6591
+ const frameDuration = frameEnd - frameStart;
6592
+ this._lastFrameDurationMs = frameDuration;
6593
+ this._lastFrameMetricsAt = frameEnd;
6594
+ if (shouldCaptureFramePerf) {
6595
+ const sectionTimes = {};
6596
+ Object.entries(_frameProfTimes)
6597
+ .filter(([key, value]) => key !== 'visibleNodes' && typeof value === 'number')
6598
+ .forEach(([key, value]) => {
6599
+ sectionTimes[key] = Number(value.toFixed(2));
6600
+ });
6601
+ this._lastFramePerfProfile = {
6602
+ durationMs: Number(frameDuration.toFixed(2)),
6603
+ update: false,
6604
+ cameraZ: Number((this.camera?.position?.z || 0).toFixed(2)),
6605
+ isZooming: this.isZooming === true,
6606
+ isPanning: this.isPanning === true,
6607
+ isCameraMoving: false,
6608
+ isInLodMode: false,
6609
+ lodBand: this.lodRenderer?._currentLodBand || null,
6610
+ visibleNodes: Number(this._visibleIds?.size || this._prevVisibleIds?.size || 0),
6611
+ freezeVisibility: true,
6612
+ freezeLod: false,
6613
+ freezeFullRes: true,
6614
+ residentMidFarStable: false,
6615
+ residentDirty: false,
6616
+ residentBandChanging: false,
6617
+ viewSyncMode: 'ua-stationary-camera-navigation',
6618
+ lodUpdateCalled: false,
6619
+ lodUpdateSkipped: true,
6620
+ lodUpdateSkipReason: 'ua-stationary-camera-navigation',
6621
+ lodSkipReason: '',
6622
+ lodDirtyReason: '',
6623
+ lodDirtyState: null,
6624
+ viewSyncBreakdown: null,
6625
+ shouldRunVisibilityCulling: false,
6626
+ visibilityStableSkip: true,
6627
+ visibilitySkipReason: 'ua-stationary-camera-navigation',
6628
+ incrementalPanVisibility: false,
6629
+ shouldSyncCss3dTransforms: false,
6630
+ shouldRenderCss3d: false,
6631
+ usedCss3dCameraFastPath: false,
6632
+ uaStationaryCameraNavigation: true,
6633
+ shouldSyncTextOverlays: false,
6634
+ usedPassiveOverlayCameraFastPath: false,
6635
+ passiveOverlaySuppressed: this._suppressPassiveDomOverlayDuringZoom === true,
6636
+ webgl: this._lastWebglRenderInfo || null,
6637
+ sections: sectionTimes
6638
+ };
6639
+ updateFramePerfPeakProfile(this, this._lastFramePerfProfile, frameEnd);
6640
+ } else if (this._lastFramePerfProfile) {
6641
+ this._lastFramePerfProfile = null;
6642
+ }
6643
+
6644
+ this._lastAnimationLoopDelayMs = CAMERA_STATIONARY_INTERACTION_FRAME_DELAY_MS;
6645
+ this._animationLoopExecuting = false;
6646
+ this._scheduleAnimationLoop(loop, CAMERA_STATIONARY_INTERACTION_FRAME_DELAY_MS);
6647
+ return;
6648
+ }
5009
6649
  const isMotionFreezeEligible =
5010
6650
  (this.isZooming === true || this.isPanning === true || isCameraMoving === true) &&
5011
6651
  !isNodeInteracting &&
5012
6652
  !hasNonCameraNavigationForcedUpdate &&
5013
6653
  !hasPendingNearCssWarmup &&
5014
6654
  !this.isWindowResizing;
6655
+ const needsPerfClassSync = shouldSyncMindCanvasPerfClasses();
5015
6656
  const approxVisibleNodeCount = Number(this._visibleIds?.size || this._prevVisibleIds?.size || 0);
5016
6657
  const totalNodeCount = Number(this.nodeObjectsById?.size || 0);
5017
6658
  const shouldUseDensePerfClass =
5018
6659
  approxVisibleNodeCount >= DENSE_VISIBLE_NODE_THRESHOLD ||
5019
6660
  totalNodeCount >= DENSE_TOTAL_NODE_THRESHOLD;
5020
- syncMindCanvasPerfClasses({
5021
- moving: isInteractiveFrame === true,
5022
- dense: shouldUseDensePerfClass
5023
- });
6661
+ if (needsPerfClassSync) {
6662
+ syncMindCanvasPerfClasses({
6663
+ moving: isInteractiveFrame === true,
6664
+ dense: shouldUseDensePerfClass
6665
+ });
6666
+ }
5024
6667
  const isMotionHeavyScene =
5025
6668
  approxVisibleNodeCount >= AUTO_FREEZE_VISIBLE_NODE_THRESHOLD ||
5026
6669
  Number(this._lastFrameDurationMs || 0) >= AUTO_FREEZE_FRAME_MS ||
@@ -5049,7 +6692,6 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5049
6692
  const shouldFreezeFullResDuringMotion =
5050
6693
  isFullResMode &&
5051
6694
  isMotionFreezeEligible &&
5052
- !hasFocusedTextOverlay &&
5053
6695
  !hasEditingOverlay &&
5054
6696
  (
5055
6697
  isUaStyleCameraTransformFrame === true ||
@@ -5160,7 +6802,11 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5160
6802
  // ▼▼▼ [Perf] CSS3D 동기화/렌더 스로틀 (정지 프레임은 스킵) ▼▼▼
5161
6803
  const hasResidentCssMediaFallback =
5162
6804
  isLodModeActive === true &&
5163
- this.lodRenderer?.shouldRenderCss3dInLod?.() === true;
6805
+ (
6806
+ (this.isPanning === true || this.isZooming === true || isCameraMoving === true)
6807
+ ? this.lodRenderer?.hasCss3dLodCandidates?.() === true
6808
+ : this.lodRenderer?.shouldRenderCss3dInLod?.() === true
6809
+ );
5164
6810
  const shouldUpdateResidentCssMediaFallback =
5165
6811
  hasResidentCssMediaFallback === true &&
5166
6812
  (
@@ -5340,12 +6986,16 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5340
6986
  );
5341
6987
 
5342
6988
  const isInLodMode = this.lodRenderer?.isInLODMode === true;
5343
- const shouldRenderCss3dInLod = isInLodMode && this.lodRenderer?.shouldRenderCss3dInLod?.() === true;
6989
+ const shouldRenderCss3dInLod = isInLodMode && (
6990
+ (this.isPanning === true || this.isZooming === true || isCameraMoving === true)
6991
+ ? this.lodRenderer?.hasCss3dLodCandidates?.() === true
6992
+ : this.lodRenderer?.shouldRenderCss3dInLod?.() === true
6993
+ );
5344
6994
  const shouldRenderPassiveDomTextOverlay =
5345
- useTextOverlayV2 &&
6995
+ isPassiveDomOverlayRuntimeEnabled &&
5346
6996
  window.MindMapTextOverlayV2?.shouldRenderPassiveDomOverlays?.(this) === true;
5347
6997
  const hasLiveInteractiveDomOverlay =
5348
- useTextOverlayV2 &&
6998
+ isPassiveDomOverlayRuntimeEnabled &&
5349
6999
  window.MindMapTextOverlayV2?.hasLiveInteractiveOverlay?.(this) === true;
5350
7000
  const hasPassiveDomOverlaySurface =
5351
7001
  shouldRenderPassiveDomTextOverlay ||
@@ -5360,6 +7010,7 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5360
7010
  )
5361
7011
  );
5362
7012
  const hasOverlayFocus = hasFocusedTextOverlay || hasEditingOverlay;
7013
+ const hasMotionBlockingOverlayFocus = hasEditingOverlay;
5363
7014
  const visibleOverlayCount = Math.max(
5364
7015
  Number(this._visibleIds?.size || 0),
5365
7016
  Number(this._prevVisibleIds?.size || 0)
@@ -5370,7 +7021,7 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5370
7021
  !isInLodMode &&
5371
7022
  !isCtrlWheelVerticalScrolling &&
5372
7023
  !isCameraNavigationOverlayPriority &&
5373
- !hasOverlayFocus &&
7024
+ !hasMotionBlockingOverlayFocus &&
5374
7025
  !isNodeInteracting &&
5375
7026
  !hasNonCameraNavigationForcedUpdate &&
5376
7027
  !hasPendingNearCssWarmup &&
@@ -5427,7 +7078,7 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5427
7078
  this._deferPassiveOverlayRefresh === true &&
5428
7079
  shouldDeferInteractivePassiveOverlayRefresh &&
5429
7080
  !isInLodMode &&
5430
- !hasOverlayFocus &&
7081
+ !hasMotionBlockingOverlayFocus &&
5431
7082
  !isNodeInteracting &&
5432
7083
  !hasNonCameraNavigationForcedUpdate &&
5433
7084
  !this.isWindowResizing;
@@ -5450,7 +7101,7 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5450
7101
  !isInLodMode &&
5451
7102
  (this.isZooming === true || this.isPanning === true || isCameraMoving) &&
5452
7103
  !isCtrlWheelVerticalScrolling &&
5453
- !hasOverlayFocus &&
7104
+ !hasMotionBlockingOverlayFocus &&
5454
7105
  !hasOverlayV2DirtyState &&
5455
7106
  !effectiveOverlayDirty &&
5456
7107
  isCameraOnlyOverlayFrame &&
@@ -5469,11 +7120,14 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5469
7120
  useTextOverlayV2 &&
5470
7121
  hasPassiveDomOverlaySurface &&
5471
7122
  !isPassiveOverlaySuppressed &&
5472
- !hasOverlayFocus &&
7123
+ !hasMotionBlockingOverlayFocus &&
5473
7124
  !hasOverlayV2DirtyState &&
5474
7125
  !this.isWindowResizing &&
5475
7126
  (this.isZooming === true || this.isPanning === true || isCameraMoving);
5476
- window.MindMapTextOverlayV2?.setMotionHint?.(this, shouldUsePassiveOverlayMotionHint);
7127
+ if (isPassiveDomOverlayRuntimeEnabled ||
7128
+ this._textOverlayV2State?.motionHintActive === true) {
7129
+ window.MindMapTextOverlayV2?.setMotionHint?.(this, shouldUsePassiveOverlayMotionHint);
7130
+ }
5477
7131
  const isPureCss3dCameraMotionFrame = !!(
5478
7132
  this.cssRenderer &&
5479
7133
  isCss3dEnabled &&
@@ -5484,12 +7138,14 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5484
7138
  !isNodeInteracting &&
5485
7139
  !hasNonCameraNavigationForcedUpdate &&
5486
7140
  !hasPendingNearCssWarmup &&
5487
- !hasOverlayFocus &&
5488
- this._css3dVisualChangedThisFrame !== true &&
5489
- this._css3dVisualChangedSinceLastRender !== true &&
7141
+ !hasMotionBlockingOverlayFocus &&
7142
+ !hasPendingCss3dStructuralTransformWork &&
5490
7143
  !lodVisualEpochChangedThisFrame &&
5491
7144
  this.isWindowResizing !== true
5492
7145
  );
7146
+ if (isPureCss3dCameraMotionFrame === true && hasPendingCss3dVisualWork) {
7147
+ this._css3dVisualDeferredForMotion = true;
7148
+ }
5493
7149
 
5494
7150
  // 4. CSS3D Position Sync
5495
7151
  // ----------------------------------------------------------------
@@ -5511,19 +7167,29 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5511
7167
  !isCameraMoving &&
5512
7168
  !isNodeInteracting &&
5513
7169
  !this.isWindowResizing;
5514
- window.MindMapNodes?.updateAnimatedImageStatusTextures?.(this, frameStart, {
5515
- allowAnimation: shouldAnimateImageStatusTextures
5516
- });
7170
+ if (this._activeImageStatusTextures?.size > 0) {
7171
+ window.MindMapNodes?.updateAnimatedImageStatusTextures?.(this, frameStart, {
7172
+ allowAnimation: shouldAnimateImageStatusTextures
7173
+ });
7174
+ }
5517
7175
 
5518
7176
  // 5. Cursor DOM position
5519
7177
  // ----------------------------------------------------------------
5520
- if (!isPureCss3dCameraMotionFrame || (this.frameCount % CAMERA_MOTION_WEBGL_FRAME_INTERVAL) === 0) {
7178
+ const shouldSampleWebglDuringCameraMotion =
7179
+ renderDebugFlags.sampleWebglDuringCameraMotion === true &&
7180
+ (this.frameCount % CAMERA_MOTION_WEBGL_FRAME_INTERVAL) === 0;
7181
+ if (!isPureCss3dCameraMotionFrame || shouldSampleWebglDuringCameraMotion) {
5521
7182
  updateCursorDomElement(this);
5522
7183
  }
5523
7184
 
5524
7185
  // 6. Theme Animation
5525
7186
  // ----------------------------------------------------------------
5526
7187
  const isGpuParticleTheme = this.currentThemeName === 'Space3D' || this.currentThemeName === 'SpiritFlow';
7188
+ const shouldUseMotionGridThemeSubstitute =
7189
+ false &&
7190
+ isCameraNavigationOnlyInteractiveFrame === true &&
7191
+ this._isMotionGridThemeEnabled() === true &&
7192
+ !isBoardLoading;
5527
7193
  const shouldForceGpuParticleAnimation =
5528
7194
  isGpuParticleTheme &&
5529
7195
  !isBoardLoading &&
@@ -5542,8 +7208,8 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5542
7208
  this._forceThemeAnimationFrame === true;
5543
7209
  const themeAnimationInterval = this._getThemeAnimationInterval(isInteractiveFrame, themeAnimationMode);
5544
7210
  const effectiveThemeAnimationInterval =
5545
- isPureCss3dCameraMotionFrame === true && forceThemeCameraUpdate !== true
5546
- ? Math.max(themeAnimationInterval, CAMERA_MOTION_WEBGL_FRAME_INTERVAL)
7211
+ isCameraNavigationOnlyInteractiveFrame === true
7212
+ ? 1
5547
7213
  : themeAnimationInterval;
5548
7214
  const shouldCheckThemeAnimation =
5549
7215
  forceThemeCameraUpdate ||
@@ -5568,40 +7234,122 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5568
7234
  this._forceThemeAnimationFrame = false;
5569
7235
  }
5570
7236
  }
5571
-
7237
+
5572
7238
  // 7. Render
5573
7239
  // ----------------------------------------------------------------
5574
7240
  _profileSection('css3dSync');
7241
+ const shouldFinalizeWebglCanvasCameraTransform = !!(
7242
+ this._webglCanvasMotionTransformActive === true &&
7243
+ isCameraNavigationOnlyInteractiveFrame !== true
7244
+ );
7245
+ const shouldUseAnimationCadenceForWebglFrame =
7246
+ isCameraNavigationOnlyInteractiveFrame === true
7247
+ ? false
7248
+ : (shouldUseAnimation60Fps === true || shouldUseAnimationLowFpsFrame === true);
5575
7249
  const shouldRenderWebglFrame =
5576
7250
  isWebglRenderEnabled &&
5577
7251
  (
5578
7252
  isBoardLoading === true ||
5579
7253
  shouldUpdate === true ||
7254
+ shouldFinalizeWebglCanvasCameraTransform === true ||
5580
7255
  isInteractiveFrame === true ||
5581
- shouldUseAnimation60Fps === true ||
5582
- shouldUseAnimationLowFpsFrame === true ||
7256
+ shouldUseAnimationCadenceForWebglFrame === true ||
5583
7257
  themeAnimatedThisFrame === true
5584
7258
  ) &&
5585
7259
  (
5586
7260
  isPureCss3dCameraMotionFrame !== true ||
5587
7261
  themeAnimatedThisFrame === true ||
5588
- (this.frameCount % CAMERA_MOTION_WEBGL_FRAME_INTERVAL) === 0
7262
+ shouldSampleWebglDuringCameraMotion === true
5589
7263
  );
7264
+ const shouldHideBackgroundThemeDuringCameraNavigation = !!(
7265
+ shouldUseMotionGridThemeSubstitute === true &&
7266
+ this.currentThemeObject &&
7267
+ this.currentThemeObject.visible !== false
7268
+ );
7269
+ let hiddenCameraMotionThemeObject = null;
7270
+ let usedWebglCanvasCameraTransform = false;
7271
+ const shouldUseWebglCanvasCameraTransformFrame = !!(
7272
+ isWebglRenderEnabled &&
7273
+ isCameraNavigationOnlyInteractiveFrame === true &&
7274
+ canUseWebglCanvasTransformForCameraMotion(this) === true &&
7275
+ shouldUpdate !== true &&
7276
+ themeAnimatedThisFrame !== true &&
7277
+ shouldUseAnimationCadenceForWebglFrame !== true &&
7278
+ !isBoardLoading &&
7279
+ this._lastWebglRenderedCameraState &&
7280
+ this.renderer?.domElement
7281
+ );
5590
7282
  if (shouldRenderWebglFrame) {
5591
- this.renderer.render(this.scene, this.camera);
5592
- const renderInfo = this.renderer?.info?.render || null;
5593
- this._lastWebglRenderInfo = renderInfo ? {
5594
- calls: Number(renderInfo.calls || 0),
5595
- triangles: Number(renderInfo.triangles || 0),
5596
- points: Number(renderInfo.points || 0),
5597
- lines: Number(renderInfo.lines || 0)
5598
- } : null;
7283
+ if (shouldUseWebglCanvasCameraTransformFrame === true &&
7284
+ this._applyWebglCanvasCameraTransform() === true) {
7285
+ usedWebglCanvasCameraTransform = true;
7286
+ this._webglCameraMotionDeferred = true;
7287
+ this._lastWebglRenderInfo = {
7288
+ calls: 0,
7289
+ triangles: 0,
7290
+ points: 0,
7291
+ lines: 0,
7292
+ skipped: true,
7293
+ webglCanvasCameraTransform: true
7294
+ };
7295
+ } else {
7296
+ this._clearWebglCanvasCameraTransform();
7297
+ if (shouldHideBackgroundThemeDuringCameraNavigation) {
7298
+ hiddenCameraMotionThemeObject = this.currentThemeObject;
7299
+ hiddenCameraMotionThemeObject.visible = false;
7300
+ }
7301
+ try {
7302
+ this.renderer.render(this.scene, this.camera);
7303
+ } finally {
7304
+ if (hiddenCameraMotionThemeObject) {
7305
+ hiddenCameraMotionThemeObject.visible = true;
7306
+ }
7307
+ }
7308
+ this._recordWebglRenderedCameraState();
7309
+ const renderInfo = this.renderer?.info?.render || null;
7310
+ this._lastWebglRenderInfo = renderInfo ? {
7311
+ calls: Number(renderInfo.calls || 0),
7312
+ triangles: Number(renderInfo.triangles || 0),
7313
+ points: Number(renderInfo.points || 0),
7314
+ lines: Number(renderInfo.lines || 0),
7315
+ backgroundThemeHidden: hiddenCameraMotionThemeObject ? true : undefined
7316
+ } : null;
7317
+ }
5599
7318
  } else if (isWebglRenderEnabled) {
5600
- this._lastWebglRenderInfo = { calls: 0, triangles: 0, points: 0, lines: 0, skipped: true };
7319
+ if (isPureCss3dCameraMotionFrame === true) {
7320
+ this._webglCameraMotionDeferred = true;
7321
+ }
7322
+ if (isCameraNavigationOnlyInteractiveFrame === true &&
7323
+ canUseWebglCanvasTransformForCameraMotion(this) === true) {
7324
+ usedWebglCanvasCameraTransform = this._applyWebglCanvasCameraTransform() === true;
7325
+ } else {
7326
+ this._clearWebglCanvasCameraTransform();
7327
+ }
7328
+ this._lastWebglRenderInfo = {
7329
+ calls: 0,
7330
+ triangles: 0,
7331
+ points: 0,
7332
+ lines: 0,
7333
+ skipped: true,
7334
+ webglCanvasCameraTransform: usedWebglCanvasCameraTransform || undefined
7335
+ };
5601
7336
  } else if (isBoardLoading) {
7337
+ this._clearWebglCanvasCameraTransform();
5602
7338
  this._lastWebglRenderInfo = { calls: 0, triangles: 0, points: 0, lines: 0 };
5603
7339
  }
5604
-
7340
+ if (shouldUseMotionGridThemeSubstitute === true &&
7341
+ isCameraNavigationOnlyInteractiveFrame === true) {
7342
+ this._syncMotionGridLayerForCameraNavigation(usedWebglCanvasCameraTransform);
7343
+ }
7344
+ if (shouldRenderWebglFrame === true &&
7345
+ this._motionGridVisible === true &&
7346
+ isPureCss3dCameraMotionFrame !== true &&
7347
+ this.isPanning !== true &&
7348
+ this.isZooming !== true &&
7349
+ isCameraMoving !== true) {
7350
+ this._syncMotionGridLayer(false);
7351
+ }
7352
+
5605
7353
  _profileSection('webglRender');
5606
7354
  // ▼▼▼ [FIX] LOD 모드(MID/FAR)에서는 CSS3D 렌더러 호출 건너뛰기 ▼▼▼
5607
7355
  // CSS3D가 모두 display:none이어도 렌더러가 813개 객체를 순회하면서 시간 소비
@@ -5632,6 +7380,7 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5632
7380
  }
5633
7381
  _profileSection('css3dRender');
5634
7382
  if (canUseCss3dCameraFastPath) {
7383
+ this._syncMotionGridLayerForCameraNavigation(usedWebglCanvasCameraTransform);
5635
7384
  this.cssRenderer.renderCamera(this.camera);
5636
7385
  }
5637
7386
  _profileSection('css3dCamera');
@@ -5644,18 +7393,32 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5644
7393
  );
5645
7394
  const hasBusinessAutomationConnectionDraft =
5646
7395
  window.MindMapCss3DManager?.hasBusinessAutomationConnectionDraft?.() === true;
5647
- const shouldThrottleBusinessAutomationEdgesForCameraMotion =
5648
- isPureCss3dCameraMotionFrame === true &&
7396
+ const shouldTransformBusinessAutomationEdgesForCameraMotion =
7397
+ hasBusinessAutomationEdgeLayer &&
5649
7398
  hasBusinessAutomationConnectionDraft !== true &&
7399
+ window.MindMapCss3DManager?.syncBusinessAutomationEdgesForCameraMotion &&
7400
+ (this.isPanning === true || this.isZooming === true || isCameraMoving === true) &&
5650
7401
  !isNodeInteracting &&
5651
- hasNonCameraNavigationForcedUpdate !== true &&
5652
- visibilityChangedThisFrame !== true &&
5653
- lodVisualEpochChangedThisFrame !== true &&
5654
- (this.frameCount % CAMERA_MOTION_WEBGL_FRAME_INTERVAL) !== 0;
7402
+ !hasNonCameraNavigationForcedUpdate &&
7403
+ !visibilityChangedThisFrame &&
7404
+ !lodVisualEpochChangedThisFrame &&
7405
+ window.MindMapCss3DManager.syncBusinessAutomationEdgesForCameraMotion(this) === true;
7406
+ const shouldThrottleBusinessAutomationEdgesForCameraMotion = shouldTransformBusinessAutomationEdgesForCameraMotion === true;
7407
+ const shouldDeferBusinessAutomationEdgesForCameraMotion = !!(
7408
+ hasBusinessAutomationEdgeLayer &&
7409
+ hasBusinessAutomationConnectionDraft !== true &&
7410
+ shouldThrottleBusinessAutomationEdgesForCameraMotion !== true &&
7411
+ (this.isPanning === true || this.isZooming === true || isCameraMoving === true) &&
7412
+ !isNodeInteracting &&
7413
+ !hasNonCameraNavigationForcedUpdate &&
7414
+ !visibilityChangedThisFrame &&
7415
+ !lodVisualEpochChangedThisFrame
7416
+ );
5655
7417
  const shouldSyncBusinessAutomationEdges =
5656
7418
  hasBusinessAutomationEdgeLayer &&
5657
7419
  window.MindMapCss3DManager?.renderBusinessAutomationEdges &&
5658
7420
  shouldThrottleBusinessAutomationEdgesForCameraMotion !== true &&
7421
+ shouldDeferBusinessAutomationEdgesForCameraMotion !== true &&
5659
7422
  (
5660
7423
  this._businessAutomationEdgeRenderActive === true ||
5661
7424
  this.isPanning === true ||
@@ -5666,7 +7429,8 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5666
7429
  visibilityChangedThisFrame === true ||
5667
7430
  lodVisualEpochChangedThisFrame === true
5668
7431
  );
5669
- if (shouldThrottleBusinessAutomationEdgesForCameraMotion === true) {
7432
+ if (shouldThrottleBusinessAutomationEdgesForCameraMotion === true ||
7433
+ shouldDeferBusinessAutomationEdgesForCameraMotion === true) {
5670
7434
  this._businessAutomationEdgesDeferredForMotion = true;
5671
7435
  }
5672
7436
  if (shouldSyncBusinessAutomationEdges) {
@@ -5684,8 +7448,8 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5684
7448
  const shouldSyncTextOverlays =
5685
7449
  effectiveOverlayDirty === true ||
5686
7450
  (useTextOverlayV2
5687
- ? (hasOverlayV2DirtyState || shouldRefreshPassiveDomTextOverlay || shouldFinalizePassiveOverlayLayout || (hasOverlayFocus && shouldUpdateCss3d))
5688
- : (hasOverlayFocus && shouldUpdateCss3d));
7451
+ ? (hasOverlayV2DirtyState || shouldRefreshPassiveDomTextOverlay || shouldFinalizePassiveOverlayLayout || (hasMotionBlockingOverlayFocus && shouldUpdateCss3d))
7452
+ : (hasMotionBlockingOverlayFocus && shouldUpdateCss3d));
5689
7453
  const shouldAttemptPassiveOverlayCameraFastPath =
5690
7454
  useTextOverlayV2 &&
5691
7455
  shouldUsePassiveOverlayCameraFastPath;
@@ -5698,7 +7462,7 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5698
7462
  const shouldDeferFullPassiveOverlayFallback =
5699
7463
  shouldFallbackToFullPassiveOverlayRefresh &&
5700
7464
  shouldRenderPassiveDomTextOverlay &&
5701
- !hasOverlayFocus &&
7465
+ !hasMotionBlockingOverlayFocus &&
5702
7466
  (!isInLodMode || hasLiveInteractiveDomOverlay) &&
5703
7467
  (isInteractiveFrame || this.isPanning === true || this.isZooming === true || visibleOverlayCount > 80);
5704
7468
  if (useTextOverlayV2 && isPassiveOverlaySuppressed) {
@@ -5717,7 +7481,7 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5717
7481
  // full overlay layout update instead of waiting for the
5718
7482
  // next deferred refresh window.
5719
7483
  window.MindMapTextOverlayV2.updateFrame(this);
5720
- if (shouldRenderPassiveDomTextOverlay && !hasOverlayFocus && (!isInLodMode || hasLiveInteractiveDomOverlay)) {
7484
+ if (shouldRenderPassiveDomTextOverlay && !hasMotionBlockingOverlayFocus && (!isInLodMode || hasLiveInteractiveDomOverlay)) {
5721
7485
  this._lastPassiveOverlayFullRefreshAt = frameStart;
5722
7486
  this._overlayDebugRefreshAgeMs = 0;
5723
7487
  this._overlayDebugRefreshDeferred = false;
@@ -5749,7 +7513,7 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5749
7513
  (this.isPanning === true || this.isZooming === true || isCameraMoving === true) &&
5750
7514
  !isNodeInteracting &&
5751
7515
  !hasNonCameraNavigationForcedUpdate &&
5752
- !hasOverlayFocus &&
7516
+ !hasMotionBlockingOverlayFocus &&
5753
7517
  !this.isWindowResizing;
5754
7518
  if (isMenuOverlayUiEnabled && window.MindMapMenuManager && shouldDeferFloatingUiDuringMotion !== true) {
5755
7519
  window.MindMapMenuManager.update();
@@ -5998,6 +7762,11 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5998
7762
  return;
5999
7763
  }
6000
7764
 
7765
+ if (!immediate && this._viewStatePersistTimer) {
7766
+ this._viewStatePersistNeedsFreshSnapshot = true;
7767
+ return;
7768
+ }
7769
+
6001
7770
  const snapshot = this.buildViewStateSnapshot();
6002
7771
  if (!snapshot) {
6003
7772
  return;
@@ -6010,6 +7779,7 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
6010
7779
 
6011
7780
  this._pendingViewStateSnapshot = snapshot;
6012
7781
  this._pendingViewStateKey = snapshotKey;
7782
+ this._viewStatePersistNeedsFreshSnapshot = false;
6013
7783
 
6014
7784
  const isCameraStillSettling = () => {
6015
7785
  if (!this.camera) {
@@ -6047,10 +7817,24 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
6047
7817
  const persist = async () => {
6048
7818
  this._viewStatePersistTimer = null;
6049
7819
  if (!immediate && isNavigationActive()) {
7820
+ this._viewStatePersistNeedsFreshSnapshot = true;
6050
7821
  armPersistTimer();
6051
7822
  return;
6052
7823
  }
6053
7824
 
7825
+ if (this._viewStatePersistNeedsFreshSnapshot === true) {
7826
+ const freshSnapshot = this.buildViewStateSnapshot();
7827
+ if (!freshSnapshot) {
7828
+ this._viewStatePersistNeedsFreshSnapshot = false;
7829
+ return;
7830
+ }
7831
+
7832
+ const freshSnapshotKey = this.buildViewStateKey(freshSnapshot);
7833
+ this._pendingViewStateSnapshot = freshSnapshot;
7834
+ this._pendingViewStateKey = freshSnapshotKey;
7835
+ this._viewStatePersistNeedsFreshSnapshot = false;
7836
+ }
7837
+
6054
7838
  const pendingSnapshot = this._pendingViewStateSnapshot;
6055
7839
  const pendingKey = this._pendingViewStateKey;
6056
7840
  if (!pendingSnapshot || !pendingKey || pendingKey === this._lastPersistedViewStateKey) {
@@ -6077,6 +7861,7 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
6077
7861
  clearTimeout(this._viewStatePersistTimer);
6078
7862
  this._viewStatePersistTimer = null;
6079
7863
  }
7864
+ this._viewStatePersistNeedsFreshSnapshot = false;
6080
7865
  void persist();
6081
7866
  return;
6082
7867
  }
@@ -6128,6 +7913,7 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
6128
7913
 
6129
7914
  hide() {
6130
7915
  this.scheduleViewStatePersist(true);
7916
+ this._syncMotionGridLayer(false);
6131
7917
  if (this.container) {
6132
7918
  if (window.MindMapMenuManager?.hideMenu) {
6133
7919
  window.MindMapMenuManager.hideMenu();
@@ -6225,11 +8011,23 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
6225
8011
  cleanupAndDispose() {
6226
8012
  if (!this.container) return;
6227
8013
  this.pauseAnimationLoop();
8014
+ if (this._motionGridHideTimer) {
8015
+ clearTimeout(this._motionGridHideTimer);
8016
+ this._motionGridHideTimer = null;
8017
+ }
8018
+ if (this._motionGridLayer) {
8019
+ this._motionGridLayer.remove();
8020
+ this._motionGridLayer = null;
8021
+ this._motionGridVisible = false;
8022
+ this._lastMotionGridKey = '';
8023
+ this._lastMotionGridTransformKey = '';
8024
+ }
6228
8025
  removeCursorDomElement(this);
6229
8026
  if (this._viewStatePersistTimer) {
6230
8027
  clearTimeout(this._viewStatePersistTimer);
6231
8028
  this._viewStatePersistTimer = null;
6232
8029
  }
8030
+ this._viewStatePersistNeedsFreshSnapshot = false;
6233
8031
 
6234
8032
  MindMapInteractions.removeEventListeners(this);
6235
8033
  MindMapDnD.removeEventListeners(this);
@@ -6429,7 +8227,9 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
6429
8227
  if (this.lodRenderer?.requestVisualRefresh) {
6430
8228
  this.lodRenderer.requestVisualRefresh('project-thumbnail-capture');
6431
8229
  }
8230
+ this._clearWebglCanvasCameraTransform();
6432
8231
  this.renderer.render(this.scene, this.camera);
8232
+ this._recordWebglRenderedCameraState();
6433
8233
  if (this.cssRenderer && typeof this.cssRenderer.render === 'function') {
6434
8234
  this.cssRenderer.render(this.cssScene || this.scene, this.camera);
6435
8235
  }