@mindexec/cli 0.2.440 → 0.2.441

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindexec/cli",
3
- "version": "0.2.440",
3
+ "version": "0.2.441",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -3697,6 +3697,55 @@ html body .css3d-resolution-wrapper:not(.lod-low).node-type-embed.selected-media
3697
3697
  0 0 0 1px rgba(var(--node-selection-glow-rgb-soft), 0.12) !important;
3698
3698
  }
3699
3699
 
3700
+ /* Selected video playback is an explicit compact action; native controls stay off. */
3701
+ .mind-map-video-playback-control {
3702
+ position: absolute;
3703
+ left: 50%;
3704
+ bottom: 10px;
3705
+ z-index: 8;
3706
+ display: none;
3707
+ align-items: center;
3708
+ gap: 6px;
3709
+ min-height: 28px;
3710
+ padding: 5px 10px;
3711
+ border: 1px solid rgba(255, 255, 255, 0.34);
3712
+ border-radius: 999px;
3713
+ background: rgba(15, 23, 42, 0.82);
3714
+ color: #f8fafc;
3715
+ box-shadow: 0 4px 14px rgba(15, 23, 42, 0.24);
3716
+ font: 600 12px/1 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
3717
+ letter-spacing: 0.01em;
3718
+ pointer-events: auto;
3719
+ cursor: pointer;
3720
+ transform: translateX(-50%);
3721
+ backdrop-filter: blur(8px);
3722
+ }
3723
+
3724
+ .mind-map-video-playback-control__icon {
3725
+ display: inline-flex;
3726
+ min-width: 10px;
3727
+ align-items: center;
3728
+ justify-content: center;
3729
+ font-size: 10px;
3730
+ }
3731
+
3732
+ .css3d-resolution-wrapper.node-type-video.selected .mind-map-video-playback-control,
3733
+ .css3d-resolution-wrapper.node-type-video.selected-media-safe .mind-map-video-playback-control {
3734
+ display: inline-flex;
3735
+ }
3736
+
3737
+ .css3d-resolution-wrapper.node-type-video.lod-low:not(.selected):not(.selected-media-safe) .mind-map-video-playback-control {
3738
+ display: none !important;
3739
+ }
3740
+
3741
+ .mind-map-video-playback-control:hover,
3742
+ .mind-map-video-playback-control:focus-visible {
3743
+ background: rgba(15, 23, 42, 0.94);
3744
+ border-color: rgba(255, 255, 255, 0.62);
3745
+ outline: 2px solid rgba(96, 165, 250, 0.85);
3746
+ outline-offset: 2px;
3747
+ }
3748
+
3700
3749
  html body .css3d-resolution-wrapper:not(.lod-low) > .map-node-bubble.selected,
3701
3750
  html body .css3d-resolution-wrapper:not(.lod-low) > .map-node-note.selected,
3702
3751
  html body .css3d-resolution-wrapper:not(.lod-low) > .map-node-note:focus-within,
@@ -7204,8 +7204,12 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
7204
7204
  window.MindMapMenuManager.hideMenu();
7205
7205
  }
7206
7206
  _profileSection('menu');
7207
- if (isMultiSelectOverlayUiEnabled && window.MindMapMultiSelect && shouldDeferFloatingUiDuringMotion !== true) {
7208
- window.MindMapMultiSelect.update();
7207
+ if (isMultiSelectOverlayUiEnabled && window.MindMapMultiSelect) {
7208
+ if (shouldDeferFloatingUiDuringMotion === true) {
7209
+ window.MindMapMultiSelect.updateForCameraMotion?.();
7210
+ } else {
7211
+ window.MindMapMultiSelect.update();
7212
+ }
7209
7213
  } else if (!isMultiSelectOverlayUiEnabled && window.MindMapMultiSelect?.hide) {
7210
7214
  window.MindMapMultiSelect.hide();
7211
7215
  }
@@ -2013,6 +2013,7 @@
2013
2013
  const preferredSource = getCss3dVideoDeferredSource(videoEl, nodeModel);
2014
2014
  if (videoEl.dataset) {
2015
2015
  videoEl.dataset.lodMediaSuspended = 'false';
2016
+ videoEl.dataset.videoPositionRestorePending = preferredSource ? 'true' : 'false';
2016
2017
  }
2017
2018
  videoEl.preload = videoEl.dataset?.lodSuspendedPreload || 'metadata';
2018
2019
 
@@ -3223,7 +3224,6 @@
3223
3224
  const normalizedIntent = normalizeVideoNodePlaybackIntent(intent) || 'pause';
3224
3225
  if (nodeModel) {
3225
3226
  nodeModel._videoPlayIntent = normalizedIntent;
3226
- setNodeMetadataValue(nodeModel, VIDEO_PLAYBACK_INTENT_METADATA_KEY, normalizedIntent);
3227
3227
  }
3228
3228
  if (videoEl?.dataset) {
3229
3229
  videoEl.autoplay = normalizedIntent === 'play';
@@ -3233,6 +3233,7 @@
3233
3233
  if (options?.persist === true) {
3234
3234
  persistVideoNodePlaybackIntent(nodeModel, normalizedIntent);
3235
3235
  }
3236
+ window.MindMapVideoPlaybackControls?.sync?.(videoEl, normalizedIntent);
3236
3237
  return normalizedIntent;
3237
3238
  }
3238
3239
 
@@ -3290,50 +3291,13 @@
3290
3291
  }
3291
3292
 
3292
3293
  function bindVideoPlaybackStateTracking(videoEl, nodeModel) {
3293
- if (!videoEl || !nodeModel) {
3294
- return;
3295
- }
3296
-
3297
- const nodeId = getNodeId(nodeModel);
3298
- if (videoEl.dataset?.videoPlaybackStateBoundNodeId === nodeId) {
3299
- return;
3300
- }
3301
-
3302
- if (videoEl.dataset) {
3303
- videoEl.dataset.videoPlaybackStateBoundNodeId = nodeId || '';
3304
- }
3305
-
3306
- const rememberTimeOnly = () => {
3307
- const currentTime = Number(videoEl.currentTime || 0);
3308
- if (Number.isFinite(currentTime) && currentTime >= 0) {
3309
- nodeModel._videoPlaybackCurrentTime = currentTime;
3310
- if (videoEl.dataset) {
3311
- videoEl.dataset.videoLastKnownTime = String(currentTime);
3312
- }
3313
- }
3314
- };
3315
-
3316
- const markPlaying = () => {
3317
- rememberVideoNodeRuntimePlayback(nodeModel, videoEl, { forceIntent: true });
3318
- setVideoNodePlaybackIntent(nodeModel, videoEl, 'play');
3319
- };
3320
-
3321
- const markPaused = () => {
3322
- rememberTimeOnly();
3323
- if (videoEl.dataset?.visibilityPaused === 'true' || videoEl.dataset?.videoResumePending === 'true') {
3324
- return;
3325
- }
3326
- setVideoNodePlaybackIntent(nodeModel, videoEl, 'pause');
3327
- };
3328
-
3329
- videoEl.addEventListener('play', markPlaying, { passive: true });
3330
- videoEl.addEventListener('playing', markPlaying, { passive: true });
3331
- videoEl.addEventListener('pause', markPaused, { passive: true });
3332
- videoEl.addEventListener('timeupdate', rememberTimeOnly, { passive: true });
3333
- videoEl.addEventListener('seeking', rememberTimeOnly, { passive: true });
3334
- videoEl.addEventListener('loadedmetadata', () => {
3335
- restoreVideoNodeRuntimePlaybackPosition(nodeModel, videoEl);
3336
- }, { passive: true });
3294
+ return window.MindMapVideoPlaybackControls?.bindState?.(videoEl, nodeModel, {
3295
+ getNodeId,
3296
+ getIntent: getVideoNodePlaybackIntent,
3297
+ rememberRuntime: rememberVideoNodeRuntimePlayback,
3298
+ restorePosition: restoreVideoNodeRuntimePlaybackPosition,
3299
+ setIntent: setVideoNodePlaybackIntent
3300
+ });
3337
3301
  }
3338
3302
 
3339
3303
  function ensureVideoNodePlaybackState(nodeModel, videoEl) {
@@ -4498,12 +4462,12 @@
4498
4462
  syncMediaLoadingRowForElement(mediaEl, nodeModel, contentTypeLower);
4499
4463
  mediaEl.dataset.assetRefreshAttempted = '';
4500
4464
  if (contentTypeLower === 'video') {
4501
- if (mediaEl.dataset?.lodMediaSuspended === 'true') {
4502
- return;
4503
- }
4504
- const playPromise = mediaEl.play?.();
4505
- if (playPromise?.catch) {
4506
- playPromise.catch(() => { });
4465
+ restoreVideoNodeRuntimePlaybackPosition(nodeModel, mediaEl);
4466
+ mediaEl.dataset.videoPositionRestorePending = 'false';
4467
+ const entry = getNodeEntryById(getNodeId(nodeModel));
4468
+ if (getVideoNodePlaybackIntent(nodeModel, mediaEl) === 'play' &&
4469
+ mediaEl.dataset?.userPaused !== 'true' && entry?.cssObject) {
4470
+ playVideoNodeForVisibility(nodeModel, entry.cssObject, mediaEl);
4507
4471
  }
4508
4472
  }
4509
4473
  };
@@ -23678,6 +23642,10 @@
23678
23642
 
23679
23643
  responseDiv.appendChild(video);
23680
23644
  responseDiv.appendChild(canvasProxy);
23645
+ window.MindMapVideoPlaybackControls?.mount?.(responseDiv, video, {
23646
+ getIntent: () => getVideoNodePlaybackIntent(nodeModel, video),
23647
+ onToggle: shouldPlay => setVideoNodePlayback(getNodeId(nodeModel), shouldPlay)
23648
+ });
23681
23649
  } else if (contentTypeLower === 'embed') {
23682
23650
  responseDiv.className = 'node-response embed-content';
23683
23651
  responseDiv.style.cssText = `width: 100%; height: 100%; display: block; position: relative; pointer-events: auto; padding: 0 !important; margin: 0 !important; background: transparent; border: 0 !important; overflow: hidden; scrollbar-width: none;`;
@@ -343,7 +343,7 @@
343
343
  return nodeEntry?.model?.contentType === 'image' ? nodeId : null;
344
344
  }
345
345
 
346
- function updateSingleImageOverlay() {
346
+ function updateSingleImageOverlay(forceWorldProjection = false) {
347
347
  if (!_module || !singleImageOverlay || !_module.camera || !_module.container) return;
348
348
 
349
349
  const nodeId = getSingleSelectedImageNodeId();
@@ -360,7 +360,10 @@
360
360
  return;
361
361
  }
362
362
 
363
- const screenRect = getNodeScreenRect(nodeEntry);
363
+ // Camera-only motion must stay on the projection fast path. Reading the
364
+ // CSS node rect here can force layout and may still reflect the previous
365
+ // CSS3D camera transform when this overlay is updated in the same frame.
366
+ const screenRect = forceWorldProjection ? null : getNodeScreenRect(nodeEntry);
364
367
  if (screenRect) {
365
368
  const screenWidth = Math.max(0, screenRect.right - screenRect.left);
366
369
  const screenHeight = Math.max(0, screenRect.bottom - screenRect.top);
@@ -1594,6 +1597,28 @@
1594
1597
  updateMultiSelectOverlay();
1595
1598
  updateSingleImageOverlay();
1596
1599
  }
1600
+
1601
+ // Camera-only fast path: currentBounds and node world frames are already
1602
+ // canonical, so reproject only the fixed overlays. This deliberately avoids
1603
+ // selected-node scans, DOM layout reads, menu work, and renderer rebuilds.
1604
+ function updateForCameraMotion() {
1605
+ if (!_module || !_module.camera || !_module.container) return false;
1606
+
1607
+ let updated = false;
1608
+ if (multiSelectOverlay &&
1609
+ multiSelectOverlay.style.display !== 'none' &&
1610
+ currentBounds) {
1611
+ updateOverlayPosition(null, true);
1612
+ updated = true;
1613
+ }
1614
+
1615
+ if (getSingleSelectedImageNodeId()) {
1616
+ updateSingleImageOverlay(true);
1617
+ updated = true;
1618
+ }
1619
+
1620
+ return updated;
1621
+ }
1597
1622
  // ▲▲▲ [Perf] ▲▲▲
1598
1623
 
1599
1624
  // ▼▼▼ [New] Dynamic menu button creation based on selection type ▼▼▼
@@ -1701,10 +1726,11 @@
1701
1726
 
1702
1727
  window.MindMapMultiSelect = {
1703
1728
  init,
1704
- showMultiSelectBounds,
1705
- hide,
1706
- update,
1707
- checkAndUpdateDisplay,
1729
+ showMultiSelectBounds,
1730
+ hide,
1731
+ update,
1732
+ updateForCameraMotion,
1733
+ checkAndUpdateDisplay,
1708
1734
  getSelectedImageNodeIds,
1709
1735
  fitTextNodesToContentAndReflowY,
1710
1736
  isResizing: () => isResizing,
@@ -0,0 +1,95 @@
1
+ // Selected video-node playback chrome. Runtime playback ownership stays in the CSS3D manager.
2
+ (function () {
3
+ 'use strict';
4
+
5
+ function sync(videoEl, intent) {
6
+ const control = videoEl?.closest?.('.video-content')?.querySelector?.('.mind-map-video-playback-control');
7
+ if (!control) return false;
8
+
9
+ const wantsPlayback = String(intent || '').toLowerCase() === 'play' &&
10
+ videoEl?.dataset?.userPaused !== 'true';
11
+ control.dataset.playbackIntent = wantsPlayback ? 'play' : 'pause';
12
+ control.setAttribute('aria-label', wantsPlayback ? 'Pause video' : 'Play video');
13
+ control.setAttribute('aria-pressed', wantsPlayback ? 'true' : 'false');
14
+ const icon = control.querySelector?.('.mind-map-video-playback-control__icon');
15
+ const status = control.querySelector?.('.mind-map-video-playback-control__status');
16
+ if (icon) icon.textContent = wantsPlayback ? '❚❚' : '▶';
17
+ if (status) status.textContent = wantsPlayback ? 'Playing' : 'Paused';
18
+ return true;
19
+ }
20
+
21
+ function mount(host, videoEl, options = {}) {
22
+ if (!host || !videoEl) return null;
23
+ const existing = host.querySelector?.('.mind-map-video-playback-control');
24
+ if (existing) return existing;
25
+
26
+ const control = document.createElement('button');
27
+ control.type = 'button';
28
+ control.className = 'mind-map-video-playback-control';
29
+ const icon = document.createElement('span');
30
+ icon.className = 'mind-map-video-playback-control__icon';
31
+ icon.setAttribute('aria-hidden', 'true');
32
+ const status = document.createElement('span');
33
+ status.className = 'mind-map-video-playback-control__status';
34
+ control.appendChild(icon);
35
+ control.appendChild(status);
36
+
37
+ const stopPointerOwnership = event => event.stopPropagation();
38
+ control.addEventListener('pointerdown', stopPointerOwnership);
39
+ control.addEventListener('mousedown', stopPointerOwnership);
40
+ control.addEventListener('dblclick', stopPointerOwnership);
41
+ control.addEventListener('click', event => {
42
+ event.preventDefault();
43
+ event.stopPropagation();
44
+ const shouldPlay = String(options.getIntent?.() || '').toLowerCase() !== 'play';
45
+ options.onToggle?.(shouldPlay);
46
+ });
47
+
48
+ host.appendChild(control);
49
+ sync(videoEl, options.getIntent?.());
50
+ return control;
51
+ }
52
+
53
+ function bindState(videoEl, nodeModel, operations = {}) {
54
+ if (!videoEl || !nodeModel) return false;
55
+ const nodeId = operations.getNodeId?.(nodeModel) || '';
56
+ if (videoEl.dataset?.videoPlaybackStateBoundNodeId === nodeId) return false;
57
+ if (videoEl.dataset) videoEl.dataset.videoPlaybackStateBoundNodeId = nodeId;
58
+
59
+ const rememberTimeOnly = () => {
60
+ if (videoEl.dataset?.lodMediaSuspended === 'true' ||
61
+ videoEl.dataset?.videoPositionRestorePending === 'true') return;
62
+ const currentTime = Number(videoEl.currentTime || 0);
63
+ if (!Number.isFinite(currentTime) || currentTime < 0) return;
64
+ nodeModel._videoPlaybackCurrentTime = currentTime;
65
+ if (videoEl.dataset) videoEl.dataset.videoLastKnownTime = String(currentTime);
66
+ };
67
+ const markPlaying = () => {
68
+ operations.rememberRuntime?.(nodeModel, videoEl, { forceIntent: true });
69
+ operations.setIntent?.(nodeModel, videoEl, 'play');
70
+ };
71
+ const markPaused = () => {
72
+ rememberTimeOnly();
73
+ if (videoEl.dataset?.visibilityPaused === 'true' ||
74
+ videoEl.dataset?.videoResumePending === 'true' ||
75
+ videoEl.dataset?.lodMediaSuspended === 'true') {
76
+ sync(videoEl, operations.getIntent?.(nodeModel, videoEl));
77
+ return;
78
+ }
79
+ operations.setIntent?.(nodeModel, videoEl, 'pause');
80
+ };
81
+
82
+ videoEl.addEventListener('play', markPlaying, { passive: true });
83
+ videoEl.addEventListener('playing', markPlaying, { passive: true });
84
+ videoEl.addEventListener('pause', markPaused, { passive: true });
85
+ videoEl.addEventListener('timeupdate', rememberTimeOnly, { passive: true });
86
+ videoEl.addEventListener('seeking', rememberTimeOnly, { passive: true });
87
+ videoEl.addEventListener('loadedmetadata', () => {
88
+ operations.restorePosition?.(nodeModel, videoEl);
89
+ if (videoEl.dataset) videoEl.dataset.videoPositionRestorePending = 'false';
90
+ }, { passive: true });
91
+ return true;
92
+ }
93
+
94
+ window.MindMapVideoPlaybackControls = { bindState, mount, sync };
95
+ })();
@@ -579,7 +579,7 @@
579
579
  }
580
580
 
581
581
  const base = '_content/MindExecution.Shared/js/';
582
- const scriptVersion = '20260713-ai-stream-edit-ownership-v934';
582
+ const scriptVersion = '20260713-selection-video-playback-v935';
583
583
  const scriptUrl = (script) => `${base}${script}?v=${scriptVersion}`;
584
584
  console.log(`[Script Loader] Shared JS version: ${scriptVersion}`);
585
585
  const criticalScripts = [
@@ -593,6 +593,7 @@
593
593
  'texture-worker-manager.js', // ??Web Worker ??좎럩?ゅ뜝???좎럩苑?
594
594
  'mind-map-texture-factory.js',
595
595
  'mind-map-glow-shader.js', // ??SDF Glow (no textures, GPU only)
596
+ 'mind-map-video-playback-controls.js',
596
597
  'mind-map-css3d-manager.js',
597
598
  'mind-map-object-manager.js',
598
599
  'mind-map-pipeline.js',
@@ -1,5 +1,5 @@
1
1
  self.assetsManifest = {
2
- "version": "+woAgnNO",
2
+ "version": "VUZJrplE",
3
3
  "assets": [
4
4
  {
5
5
  "hash": "sha256-+CSYMcqLNTsq3VnH11jgYyOCCdxvHzL74CBmo4sCmMU=",
@@ -42,7 +42,7 @@
42
42
  "url": "_content/MindExecution.Shared/css/app.css"
43
43
  },
44
44
  {
45
- "hash": "sha256-4ZkJxfoTdJl937jfE4WfyEk/KLh/XxZ2y9bPWUdzpd8=",
45
+ "hash": "sha256-nGJWFhmmbUfPqx/4zncFwuiGLwqyJNR0Uet8NCG39fA=",
46
46
  "url": "_content/MindExecution.Shared/css/mind-map-overrides.css"
47
47
  },
48
48
  {
@@ -78,7 +78,7 @@
78
78
  "url": "_content/MindExecution.Shared/js/marked.min.js"
79
79
  },
80
80
  {
81
- "hash": "sha256-bBRq3Vtbchai8bpYEoThsNN2ONQiy3ktOmgeKe0U6lI=",
81
+ "hash": "sha256-hxUthw2aeZKkPCvKaa1jYyAkNB4xLAOkRMq795teC0U=",
82
82
  "url": "_content/MindExecution.Shared/js/mind-map-core.js"
83
83
  },
84
84
  {
@@ -86,7 +86,7 @@
86
86
  "url": "_content/MindExecution.Shared/js/mind-map-core.js.backup"
87
87
  },
88
88
  {
89
- "hash": "sha256-dr+i09+KUFx+ddmSDqilzqJD0FDVye29n24Xz+YY1WU=",
89
+ "hash": "sha256-V3om7yYRGnEVFwXSaNqT+tzvnLAFzHpQN5+M0RahIxU=",
90
90
  "url": "_content/MindExecution.Shared/js/mind-map-css3d-manager.js"
91
91
  },
92
92
  {
@@ -126,7 +126,7 @@
126
126
  "url": "_content/MindExecution.Shared/js/mind-map-menu-manager.js"
127
127
  },
128
128
  {
129
- "hash": "sha256-vGcZ0vzKIRaHgJBk3MMivnsyJ5WMrzCrhILNFDaIfX4=",
129
+ "hash": "sha256-wS7aOK89A/IvLSPLyxFOqkCcnPbqLgFl2Oc+bl8GLBU=",
130
130
  "url": "_content/MindExecution.Shared/js/mind-map-multi-select.js"
131
131
  },
132
132
  {
@@ -169,6 +169,10 @@
169
169
  "hash": "sha256-aPoxruQaOIkX+R/NXEtKYw5OBa0hRFmVaSw13NgF7mU=",
170
170
  "url": "_content/MindExecution.Shared/js/mind-map-texture-factory.js.backup"
171
171
  },
172
+ {
173
+ "hash": "sha256-rgRmzBpaC5Jv9JJH1ZaW7i4IRUthOfik8McRSyuXMHs=",
174
+ "url": "_content/MindExecution.Shared/js/mind-map-video-playback-controls.js"
175
+ },
172
176
  {
173
177
  "hash": "sha256-MuqzAuC7Wyo7GxkOtpclozmNB+Pu9wC4txMN4I+jM84=",
174
178
  "url": "_content/MindExecution.Shared/js/mind-map-visibility-worker.js"
@@ -830,7 +834,7 @@
830
834
  "url": "icon-512.png"
831
835
  },
832
836
  {
833
- "hash": "sha256-x3feeu4dFQjGF+xdT9qMumctaRJpYoC1pplyiuUZyzU=",
837
+ "hash": "sha256-ofd66howCQblx2Uy6MHTwD8Hwb1zYJZ0E9NtIBUxi3o=",
834
838
  "url": "index.html"
835
839
  },
836
840
  {
@@ -1,4 +1,4 @@
1
- /* Manifest version: +woAgnNO */
1
+ /* Manifest version: VUZJrplE */
2
2
  // Hosted deployments should prefer the network over stale offline caches.
3
3
  // This service worker immediately clears old Blazor offline caches and unregisters itself.
4
4