@mindexec/cli 0.2.455 → 0.2.457

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 (23) hide show
  1. package/package.json +77 -77
  2. package/remote-fast/osx-arm64/mindexec-remote-fast.dll +0 -0
  3. package/remote-fast/osx-x64/mindexec-remote-fast.dll +0 -0
  4. package/remote-fast/win-x64/mindexec-remote-fast.dll +0 -0
  5. package/scripts/remote-fleet-render-smoke.mjs +24 -46
  6. package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +4 -4
  7. package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +253 -261
  8. package/wwwroot/_content/MindExecution.Shared/js/mind-map-pipeline.js +1326 -1323
  9. package/wwwroot/_content/MindExecution.Shared/js/mind-map-render-plan.js +25 -6
  10. package/wwwroot/_framework/{MindExecution.Core.3y861y22o4.dll → MindExecution.Core.speado072l.dll} +0 -0
  11. package/wwwroot/_framework/{MindExecution.Kernel.182clfthnx.dll → MindExecution.Kernel.mjy31ssdac.dll} +0 -0
  12. package/wwwroot/_framework/{MindExecution.Plugins.Admin.ghxhrm0vf5.dll → MindExecution.Plugins.Admin.vbjjmdaao2.dll} +0 -0
  13. package/wwwroot/_framework/{MindExecution.Plugins.Business.doimlros51.dll → MindExecution.Plugins.Business.0cr5jc6wqe.dll} +0 -0
  14. package/wwwroot/_framework/{MindExecution.Plugins.Concept.hp54u7s3y5.dll → MindExecution.Plugins.Concept.m7p666e0jd.dll} +0 -0
  15. package/wwwroot/_framework/{MindExecution.Plugins.Directory.wjfnb1nk3b.dll → MindExecution.Plugins.Directory.kmdtirnljl.dll} +0 -0
  16. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.447psj7gxo.dll → MindExecution.Plugins.PlanMaster.m1pfeozacj.dll} +0 -0
  17. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.7f551zknhn.dll → MindExecution.Plugins.YouTube.nvsc9q4s6b.dll} +0 -0
  18. package/wwwroot/_framework/{MindExecution.Shared.aftyr3f4lo.dll → MindExecution.Shared.9jaznballd.dll} +0 -0
  19. package/wwwroot/_framework/{MindExecution.Web.tkj3z0fg8f.dll → MindExecution.Web.k5jxwawryl.dll} +0 -0
  20. package/wwwroot/_framework/blazor.boot.json +21 -21
  21. package/wwwroot/index.html +1 -1
  22. package/wwwroot/service-worker-assets.js +27 -27
  23. package/wwwroot/service-worker.js +1 -1
@@ -1,9 +1,9 @@
1
- // File: mind-map-css3d-manager.js
2
- // [Refactor] Owns CSS3DObject creation, DOM cloning, and event binding.
3
- (function () {
4
- 'use strict';
5
-
6
- // ▼▼▼ [Perf] Debug logging (disable in production) ▼▼▼
1
+ // File: mind-map-css3d-manager.js
2
+ // [Refactor] Owns CSS3DObject creation, DOM cloning, and event binding.
3
+ (function () {
4
+ 'use strict';
5
+
6
+ // ▼▼▼ [Perf] Debug logging (disable in production) ▼▼▼
7
7
  const DEBUG = false;
8
8
  const log = DEBUG ? console.log.bind(console) : () => { };
9
9
  const warn = DEBUG ? console.warn.bind(console) : () => { };
@@ -46,8 +46,9 @@
46
46
  const CSS3D_ONLY_NODE_RENDERING = true;
47
47
  const CSV_TABLE_MAX_RENDER_ROWS = 500;
48
48
  const CSV_TABLE_MAX_RENDER_COLUMNS = 80;
49
- const REMOTE_FLEET_DISPLAY_NAME = 'Multi Desktop Monitor';
50
- const REMOTE_FLEET_LEGACY_DISPLAY_NAME = 'Remote Fleet Monitor';
49
+ const REMOTE_FLEET_DISPLAY_NAME = 'Remote Work Console';
50
+ const REMOTE_FLEET_LEGACY_DISPLAY_NAME = 'Multi Desktop Monitor';
51
+ const REMOTE_FLEET_LEGACY_DISPLAY_NAME_V0 = 'Remote Fleet Monitor';
51
52
  const _cssVideoProxyStateByVideo = new WeakMap();
52
53
 
53
54
  function syncMindCanvasPlatformRenderClasses() {
@@ -355,12 +356,12 @@
355
356
  renderCsvTableContent
356
357
  };
357
358
  // ▲▲▲ [Perf] ▲▲▲
358
-
359
- // ▼▼▼ [New] 기본 마크다운 스타일 주입 ▼▼▼
359
+
360
+ // ▼▼▼ [New] 기본 마크다운 스타일 주입 ▼▼▼
360
361
  function injectMarkdownStyles() {
361
- // ▼▼▼ [FIX] 기존 스타일 제거 후 새로 주입 (업데이트 보장) ▼▼▼
362
- const existingStyle = document.getElementById('mind-map-markdown-styles');
363
- if (existingStyle) existingStyle.remove();
362
+ // ▼▼▼ [FIX] 기존 스타일 제거 후 새로 주입 (업데이트 보장) ▼▼▼
363
+ const existingStyle = document.getElementById('mind-map-markdown-styles');
364
+ if (existingStyle) existingStyle.remove();
364
365
  const style = document.createElement('style');
365
366
  style.id = 'mind-map-markdown-styles';
366
367
  style.textContent = `
@@ -388,17 +389,17 @@
388
389
  .markdown-body h2 { font-size: 1.4em; font-weight: bold; margin-top: 1em; margin-bottom: 0.5em; border-bottom: 1px solid #eaecef; padding-bottom: 0.3em; }
389
390
  .markdown-body h3 { font-size: 1.2em; font-weight: bold; margin-top: 1em; margin-bottom: 0.5em; }
390
391
  .markdown-body p { margin-bottom: 0.8em; white-space: normal; }
391
- .markdown-body ul { padding-left: 1.5em; margin-bottom: 1em; list-style-type: disc; }
392
- .markdown-body ol { padding-left: 1.5em; margin-bottom: 1em; list-style-type: decimal; }
393
- .markdown-body li { margin-bottom: 0.25em; }
394
- .markdown-body blockquote { border-left: 4px solid #dfe2e5; padding: 0 1em; color: #6a737d; margin: 0 0 1em 0; }
395
- .markdown-body code { font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; background-color: rgba(27,31,35,0.05); padding: 0.2em 0.4em; border-radius: 3px; font-size: 85%; }
396
- .markdown-body pre { background-color: #f6f8fa; padding: 12px; overflow: auto; border-radius: 3px; margin-bottom: 1em; }
397
- .markdown-body pre code { background-color: transparent; padding: 0; font-size: 100%; }
398
- .markdown-body a { color: #0366d6; text-decoration: none; }
399
- .markdown-body a:hover { text-decoration: underline; }
400
- .markdown-body img { max-width: 100%; box-sizing: content-box; background-color: #fff; }
401
- .markdown-body table { border-spacing: 0; border-collapse: collapse; width: 100%; margin-bottom: 1em; }
392
+ .markdown-body ul { padding-left: 1.5em; margin-bottom: 1em; list-style-type: disc; }
393
+ .markdown-body ol { padding-left: 1.5em; margin-bottom: 1em; list-style-type: decimal; }
394
+ .markdown-body li { margin-bottom: 0.25em; }
395
+ .markdown-body blockquote { border-left: 4px solid #dfe2e5; padding: 0 1em; color: #6a737d; margin: 0 0 1em 0; }
396
+ .markdown-body code { font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; background-color: rgba(27,31,35,0.05); padding: 0.2em 0.4em; border-radius: 3px; font-size: 85%; }
397
+ .markdown-body pre { background-color: #f6f8fa; padding: 12px; overflow: auto; border-radius: 3px; margin-bottom: 1em; }
398
+ .markdown-body pre code { background-color: transparent; padding: 0; font-size: 100%; }
399
+ .markdown-body a { color: #0366d6; text-decoration: none; }
400
+ .markdown-body a:hover { text-decoration: underline; }
401
+ .markdown-body img { max-width: 100%; box-sizing: content-box; background-color: #fff; }
402
+ .markdown-body table { border-spacing: 0; border-collapse: collapse; width: 100%; margin-bottom: 1em; }
402
403
  .markdown-body table th, .markdown-body table td { padding: 6px 13px; border: 1px solid #dfe2e5; }
403
404
  .markdown-body table th { font-weight: 600; background-color: #f6f8fa; }
404
405
  .markdown-body hr { height: 0.25em; padding: 0; margin: 24px 0; background-color: #e1e4e8; border: 0; }
@@ -597,10 +598,10 @@
597
598
  }
598
599
  /* Force list markers to be visible */
599
600
  .markdown-body ul li { list-style-type: disc; }
600
- .markdown-body ol li { list-style-type: decimal; }
601
-
602
- /* ▼▼▼ [NEW] 슬림 스크롤바 스타일 - 모든 텍스트/노트/코드 영역 (Razor + JS 동적노드 전체) ▼▼▼ */
603
- /* 1. Firefox 대응 + 여백 최소화 (속성 선택자로 모든 노드 잡기) */
601
+ .markdown-body ol li { list-style-type: decimal; }
602
+
603
+ /* ▼▼▼ [NEW] 슬림 스크롤바 스타일 - 모든 텍스트/노트/코드 영역 (Razor + JS 동적노드 전체) ▼▼▼ */
604
+ /* 1. Firefox 대응 + 여백 최소화 (속성 선택자로 모든 노드 잡기) */
604
605
  .note-textarea,
605
606
  .note-content,
606
607
  .markdown-body,
@@ -659,7 +660,7 @@
659
660
  width: 8px !important;
660
661
  height: 8px !important;
661
662
  }
662
-
663
+
663
664
  .note-textarea::-webkit-scrollbar-thumb,
664
665
  .note-content::-webkit-scrollbar-thumb,
665
666
  .markdown-body::-webkit-scrollbar-thumb,
@@ -750,7 +751,7 @@
750
751
  .map-node-code .code-body::-webkit-scrollbar-thumb:active {
751
752
  background: rgba(0, 0, 0, 0.45) !important;
752
753
  }
753
-
754
+
754
755
  /* 3. 노트 편집기는 렌더링 markdown과 같은 타이포그래피/박스 모델을 유지 */
755
756
  .map-node-note .note-textarea,
756
757
  .map-node-note textarea.note-textarea {
@@ -788,7 +789,7 @@
788
789
  color: transparent !important;
789
790
  text-shadow: 0 0 0 #ffffff !important;
790
791
  }
791
-
792
+
792
793
  .prose, [id^="node-response-"]:not(.image-content):not(.video-content):not(.embed-content) {
793
794
  padding-right: 14px !important;
794
795
  }
@@ -809,8 +810,8 @@
809
810
  overflow-wrap: inherit !important;
810
811
  }
811
812
  /* ▲▲▲ [NEW] ▲▲▲ */
812
-
813
- /* ▼▼▼ [NEW] CSS3D 텍스트 앨리어싱 개선 ▼▼▼ */
813
+
814
+ /* ▼▼▼ [NEW] CSS3D 텍스트 앨리어싱 개선 ▼▼▼ */
814
815
  .css3d-resolution-wrapper,
815
816
  .css3d-dynamic-node,
816
817
  [id^="css3d-node-"],
@@ -13458,7 +13459,7 @@
13458
13459
 
13459
13460
  function getRemoteFleetDisplayTitle(value) {
13460
13461
  const title = String(value ?? '').trim();
13461
- if (!title || title === REMOTE_FLEET_LEGACY_DISPLAY_NAME) {
13462
+ if (!title || title === REMOTE_FLEET_LEGACY_DISPLAY_NAME || title === REMOTE_FLEET_LEGACY_DISPLAY_NAME_V0) {
13462
13463
  return REMOTE_FLEET_DISPLAY_NAME;
13463
13464
  }
13464
13465
  return title;
@@ -13552,7 +13553,7 @@
13552
13553
 
13553
13554
  const titleBlock = document.createElement('div');
13554
13555
  titleBlock.className = 'template-card__title-block';
13555
- titleBlock.appendChild(createTemplateCardText('div', 'template-card__eyebrow', 'Remote'));
13556
+ titleBlock.appendChild(createTemplateCardText('div', 'template-card__eyebrow', 'Remote Work'));
13556
13557
  titleBlock.appendChild(createTemplateCardText('div', 'template-card__title', title));
13557
13558
  header.appendChild(titleBlock);
13558
13559
  shell.appendChild(header);
@@ -14605,7 +14606,7 @@
14605
14606
  const REMOTE_FLEET_MONITOR_REFRESH_MS = 10000;
14606
14607
  const REMOTE_FLEET_EMPTY_MONITOR_REFRESH_MS = 2500;
14607
14608
  const REMOTE_FLEET_LIVE_REFRESH_MS = 30000;
14608
- const REMOTE_FLEET_MONITOR_LIVE_FPS = 20;
14609
+ const REMOTE_FLEET_MONITOR_LIVE_FPS = 2;
14609
14610
  const REMOTE_FLEET_LIVE_FRAME_REFRESH_MS = Math.round(1000 / REMOTE_FLEET_MONITOR_LIVE_FPS);
14610
14611
  const REMOTE_FLEET_CONTROL_LIVE_FPS = 30;
14611
14612
  const REMOTE_FLEET_CONTROL_FRAME_REFRESH_MS = Math.round(1000 / REMOTE_FLEET_CONTROL_LIVE_FPS);
@@ -15690,12 +15691,13 @@
15690
15691
  }
15691
15692
  session.bodies.add(bodyView);
15692
15693
  session.getDeviceIds = getDeviceIds;
15693
- session.options = { fps: 20, width: Number(options.width || 1920) || 1920, height: Number(options.height || 1080) || 1080, tileWidth: Number(options.tileWidth || 320) || 320, tileHeight: Number(options.tileHeight || 180) || 180 };
15694
+ const fps = Math.max(1, Math.min(30, Number(options.fps || REMOTE_FLEET_MONITOR_LIVE_FPS) || REMOTE_FLEET_MONITOR_LIVE_FPS));
15695
+ session.options = { fps, width: Number(options.width || 1920) || 1920, height: Number(options.height || 1080) || 1080, tileWidth: Number(options.tileWidth || 320) || 320, tileHeight: Number(options.tileHeight || 180) || 180 };
15694
15696
  const fallback = () => {
15695
15697
  if (session.fallbackStarted) return;
15696
15698
  session.fallbackStarted = true;
15697
15699
  try { session.ws?.close?.(); } catch { /* encoder failure fallback */ }
15698
- session.bodies.forEach(body => startRemoteFleetBinaryFrameSocket(body, session.getDeviceIds, { autoStartLive: true, fps: 20, maxWidth: 960, maxHeight: 540, quality: 60, mode: 'mode2-lzo', streamPurpose: 'wall' }));
15700
+ session.bodies.forEach(body => startRemoteFleetBinaryFrameSocket(body, session.getDeviceIds, { autoStartLive: true, fps: session.options.fps, maxWidth: 960, maxHeight: 540, quality: 60, mode: 'mode2-lzo', streamPurpose: 'wall' }));
15699
15701
  window.RuntimeTrace?.emit?.('remote.atlas.fallback', { nodeId: session.nodeId, mode: 'mode2-lzo' });
15700
15702
  };
15701
15703
  session.fallback = fallback;
@@ -15709,7 +15711,7 @@
15709
15711
  ws.binaryType = 'arraybuffer';
15710
15712
  ws.onopen = () => {
15711
15713
  session.wsOpening = false;
15712
- ws.send(JSON.stringify({ type: 'configure', deviceIds: getRemoteFleetBinaryFrameDeviceIds(session), fps: 20, width: session.options.width, height: session.options.height, tileWidth: session.options.tileWidth, tileHeight: session.options.tileHeight }));
15714
+ ws.send(JSON.stringify({ type: 'configure', deviceIds: getRemoteFleetBinaryFrameDeviceIds(session), fps: session.options.fps, width: session.options.width, height: session.options.height, tileWidth: session.options.tileWidth, tileHeight: session.options.tileHeight }));
15713
15715
  window.RuntimeTrace?.emit?.('remote.atlas.wsOpen', { nodeId: session.nodeId });
15714
15716
  };
15715
15717
  ws.onmessage = event => {
@@ -17273,16 +17275,6 @@
17273
17275
  }
17274
17276
 
17275
17277
  binarySession.controlSessions.add(controlSession);
17276
- binarySession.liveOptions = {
17277
- ...(binarySession.liveOptions || {}),
17278
- autoStartLive: true,
17279
- fps: REMOTE_FLEET_CONTROL_LIVE_FPS,
17280
- maxWidth: 1280,
17281
- maxHeight: 720,
17282
- quality: 65,
17283
- mode: 'mode3-h264-hw',
17284
- streamPurpose: 'control'
17285
- };
17286
17278
  controlSession.binaryFrameSession = binarySession;
17287
17279
  controlSession.binaryFramePreferred = true;
17288
17280
  refreshRemoteFleetBinaryFrameSubscription(binarySession, true);
@@ -17368,7 +17360,7 @@
17368
17360
  session.canvas.dataset.remoteFleetFrameSeq = String(frameSeq || 0);
17369
17361
  session.canvas.style.display = 'block';
17370
17362
  session.lastControlFrameAt = Date.now();
17371
- session.status.textContent = `Control ${REMOTE_FLEET_CONTROL_LIVE_FPS}fps`;
17363
+ session.status.textContent = `${session.statusLabel || 'Control'} ${Number(session.fps || REMOTE_FLEET_CONTROL_LIVE_FPS)}fps`;
17372
17364
  session.placeholder?.remove?.();
17373
17365
  session.placeholder = null;
17374
17366
  }
@@ -17403,7 +17395,7 @@
17403
17395
  session.canvas.dataset.remoteFleetFrameUrl = String(frame.frameUrl || '');
17404
17396
  session.canvas.style.display = 'block';
17405
17397
  session.lastControlFrameAt = Date.now();
17406
- session.status.textContent = `Control ${REMOTE_FLEET_CONTROL_LIVE_FPS}fps`;
17398
+ session.status.textContent = `${session.statusLabel || 'Control'} ${Number(session.fps || REMOTE_FLEET_CONTROL_LIVE_FPS)}fps`;
17407
17399
  session.placeholder?.remove?.();
17408
17400
  session.placeholder = null;
17409
17401
  if (typeof bitmap.close === 'function') {
@@ -18152,7 +18144,9 @@
18152
18144
  wasLiveActive: false,
18153
18145
  startedStream: false,
18154
18146
  streamId: '',
18155
- controlLeaseId: `control-node-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
18147
+ controlLeaseId: `observe-node-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
18148
+ statusLabel: 'Observe',
18149
+ fps: REMOTE_FLEET_MONITOR_LIVE_FPS,
18156
18150
  timer: null,
18157
18151
  moveTimer: null,
18158
18152
  pendingMove: null,
@@ -18170,25 +18164,21 @@
18170
18164
  };
18171
18165
 
18172
18166
  bodyView._remoteFleetControlNodeSession = session;
18173
- bindRemoteFleetControlInput(session, {
18174
- captureOverlayEvents: false,
18175
- windowKeyboardCapture: false
18176
- });
18177
- ensureRemoteFleetControlInputSocket(session);
18178
18167
  startRemoteFleetBinaryFrameSocket(bodyView, () => [normalizedDeviceId], {
18179
18168
  autoStartLive: true,
18180
- fps: REMOTE_FLEET_CONTROL_LIVE_FPS,
18181
- maxWidth: 1280,
18182
- maxHeight: 720,
18183
- quality: 65,
18184
- mode: 'mode3-h264-hw'
18169
+ fps: REMOTE_FLEET_MONITOR_LIVE_FPS,
18170
+ maxWidth: 960,
18171
+ maxHeight: 540,
18172
+ quality: 60,
18173
+ mode: 'mode2-lzo',
18174
+ streamPurpose: 'wall'
18185
18175
  });
18186
18176
  attachRemoteFleetControlBinaryFrameSession(session, bodyView);
18187
18177
  stage.focus?.({ preventScroll: true });
18188
18178
  if (status) {
18189
- status.textContent = `Control ${REMOTE_FLEET_CONTROL_LIVE_FPS}fps`;
18179
+ status.textContent = `Observe ${REMOTE_FLEET_MONITOR_LIVE_FPS}fps`;
18190
18180
  }
18191
- window.RuntimeTrace?.emit?.('remote.control.nodeOpened', {
18181
+ window.RuntimeTrace?.emit?.('remote.observe.nodeOpened', {
18192
18182
  nodeId: normalizedNodeId,
18193
18183
  deviceId: normalizedDeviceId
18194
18184
  });
@@ -18440,7 +18430,7 @@
18440
18430
 
18441
18431
  if (!device || !deviceId) {
18442
18432
  const empty = document.createElement('div');
18443
- empty.textContent = lastError || 'Remote control device snapshot is unavailable.';
18433
+ empty.textContent = lastError || 'Remote workstation snapshot is unavailable.';
18444
18434
  empty.style.cssText = `
18445
18435
  flex: 1 1 auto;
18446
18436
  display: flex;
@@ -18456,7 +18446,7 @@
18456
18446
  text-align: center;
18457
18447
  background: rgba(255, 255, 255, 0.74);
18458
18448
  `;
18459
- const refreshButton = createRemoteFleetButton('Refresh', 'Refresh this remote control device', 'refresh-device');
18449
+ const refreshButton = createRemoteFleetButton('Refresh', 'Refresh this remote workstation', 'refresh-device');
18460
18450
  refreshButton.style.height = '32px';
18461
18451
  refreshButton.addEventListener('click', async event => {
18462
18452
  event.preventDefault();
@@ -18579,7 +18569,7 @@
18579
18569
  controlStage.dataset.remoteFleetControlNodeStage = 'true';
18580
18570
  controlStage.dataset.deviceId = deviceId;
18581
18571
  controlStage.tabIndex = 0;
18582
- controlStage.title = `${name} remote control`;
18572
+ controlStage.title = `${name} observation and remote work`;
18583
18573
  controlStage.style.cssText = `
18584
18574
  position: relative;
18585
18575
  flex: 1 1 auto;
@@ -18589,7 +18579,7 @@
18589
18579
  border-radius: 0;
18590
18580
  background: #020617;
18591
18581
  border: 1px solid rgba(15, 23, 42, 0.20);
18592
- cursor: crosshair;
18582
+ cursor: default;
18593
18583
  touch-action: none;
18594
18584
  outline: none;
18595
18585
  `;
@@ -18637,7 +18627,7 @@
18637
18627
  `;
18638
18628
  const controlPlaceholder = document.createElement('div');
18639
18629
  controlPlaceholder.dataset.remoteFleetControlNodePlaceholder = 'true';
18640
- controlPlaceholder.textContent = connected ? 'Waiting for screen' : 'Offline';
18630
+ controlPlaceholder.textContent = connected ? 'Waiting for observation frame' : 'Offline';
18641
18631
  controlPlaceholder.style.cssText = `
18642
18632
  position: absolute;
18643
18633
  inset: 0;
@@ -18672,7 +18662,7 @@
18672
18662
  if (session && restoredControlSurface) {
18673
18663
  session.lastControlFrameSeq = Number(preservedControlSurface?.seq || session.lastControlFrameSeq || 0) || 0;
18674
18664
  session.lastControlFrameAt = Date.now();
18675
- controlStatus.textContent = `Control ${REMOTE_FLEET_CONTROL_LIVE_FPS}fps`;
18665
+ controlStatus.textContent = `Observe ${REMOTE_FLEET_MONITOR_LIVE_FPS}fps`;
18676
18666
  window.RuntimeTrace?.emit?.('remote.control.surfaceRestored', {
18677
18667
  nodeId,
18678
18668
  deviceId,
@@ -18692,7 +18682,7 @@
18692
18682
  }
18693
18683
  }
18694
18684
 
18695
- const refreshButton = createRemoteFleetButton('Refresh', 'Refresh this remote control device', 'refresh-device');
18685
+ const refreshButton = createRemoteFleetButton('Refresh', 'Refresh this remote workstation', 'refresh-device');
18696
18686
  refreshButton.style.height = '26px';
18697
18687
  actions.appendChild(refreshButton);
18698
18688
  if (connected && thumbnailEnabled) {
@@ -18733,8 +18723,8 @@
18733
18723
  right: 8px;
18734
18724
  top: 42px;
18735
18725
  z-index: 6;
18736
- width: min(360px, calc(100% - 16px));
18737
- display: none;
18726
+ width: min(520px, calc(100% - 16px));
18727
+ display: grid;
18738
18728
  grid-template-columns: minmax(0, 1fr) auto auto;
18739
18729
  gap: 6px;
18740
18730
  align-items: stretch;
@@ -18995,7 +18985,7 @@
18995
18985
  const recentTaskBatches = parseRemoteFleetRecentTaskBatches(nodeModel);
18996
18986
  const latestTaskBatchKey = getRemoteFleetTaskBatchKey(latestTaskBatch);
18997
18987
  const latestTaskBatchActive = isRemoteFleetTaskBatchActive(latestTaskBatch);
18998
- const showTaskSummarySurfaces = false;
18988
+ const showTaskSummarySurfaces = true;
18999
18989
  if (latestTaskBatchKey && bodyView.dataset.remoteFleetTaskFollowKey !== latestTaskBatchKey) {
19000
18990
  bodyView.dataset.remoteFleetTaskFollowKey = latestTaskBatchKey;
19001
18991
  bodyView.dataset.remoteFleetTaskFollowTicks = '0';
@@ -19147,7 +19137,7 @@
19147
19137
  const searchInput = document.createElement('input');
19148
19138
  searchInput.type = 'search';
19149
19139
  searchInput.value = searchState;
19150
- searchInput.placeholder = '';
19140
+ searchInput.placeholder = 'Find a computer or task';
19151
19141
  searchInput.dataset.remoteFleetSearch = 'true';
19152
19142
  searchInput.autocomplete = 'off';
19153
19143
  searchInput.spellcheck = false;
@@ -19220,11 +19210,11 @@
19220
19210
  filterRow.appendChild(groupSelect);
19221
19211
  filterRow.appendChild(densitySelect);
19222
19212
  filterRow.appendChild(matchCount);
19223
- filterRow.dataset.remoteFleetAdvancedFilters = 'hidden';
19213
+ filterRow.dataset.remoteFleetAdvancedFilters = 'visible';
19224
19214
 
19225
19215
  const taskRow = document.createElement('div');
19226
19216
  taskRow.style.cssText = `
19227
- display: none;
19217
+ display: grid;
19228
19218
  grid-template-columns: minmax(0, 1fr) auto auto auto;
19229
19219
  gap: 8px;
19230
19220
  align-items: stretch;
@@ -19232,7 +19222,7 @@
19232
19222
  `;
19233
19223
  const taskInput = document.createElement('textarea');
19234
19224
  taskInput.dataset.remoteFleetTaskInput = 'true';
19235
- taskInput.placeholder = '';
19225
+ taskInput.placeholder = 'Describe the work: inspect Git, run tests, build, deploy, or summarize results';
19236
19226
  taskInput.rows = 2;
19237
19227
  taskInput.style.cssText = `
19238
19228
  width: 100%;
@@ -19294,7 +19284,7 @@
19294
19284
  taskRow.appendChild(aiToggleLabel);
19295
19285
  taskRow.appendChild(sendVisibleButton);
19296
19286
  taskRow.appendChild(sendConnectedButton);
19297
- taskRow.dataset.remoteFleetTaskComposer = 'hidden';
19287
+ taskRow.dataset.remoteFleetTaskComposer = 'visible';
19298
19288
 
19299
19289
  const taskFeedback = document.createElement('div');
19300
19290
  taskFeedback.dataset.remoteFleetTaskFeedback = 'true';
@@ -19313,6 +19303,8 @@
19313
19303
  text-overflow: ellipsis;
19314
19304
  white-space: nowrap;
19315
19305
  `;
19306
+ bodyView.appendChild(filterRow);
19307
+ bodyView.appendChild(taskRow);
19316
19308
  bodyView.appendChild(taskFeedback);
19317
19309
 
19318
19310
  if (showTaskSummarySurfaces && latestTaskBatch) {
@@ -19670,7 +19662,7 @@
19670
19662
  card.dataset.remoteFleetAction = 'select-device';
19671
19663
  card.setAttribute('role', 'button');
19672
19664
  card.setAttribute('aria-pressed', isSelected ? 'true' : 'false');
19673
- card.setAttribute('aria-label', `Open remote control for ${name}`);
19665
+ card.setAttribute('aria-label', `Open remote workstation for ${name}`);
19674
19666
  card.tabIndex = 0;
19675
19667
  card.title = getRemoteFleetDeviceTooltip(device);
19676
19668
  card.style.cssText = `
@@ -20188,11 +20180,11 @@
20188
20180
  }
20189
20181
  setTaskFeedback('');
20190
20182
  } else {
20191
- setTaskFeedback(result?.error || result?.Error || 'Remote control node failed.', 'error');
20183
+ setTaskFeedback(result?.error || result?.Error || 'Remote workstation node failed.', 'error');
20192
20184
  }
20193
20185
  })
20194
20186
  .catch(error => {
20195
- setTaskFeedback(error?.message || 'Remote control node failed.', 'error');
20187
+ setTaskFeedback(error?.message || 'Remote workstation node failed.', 'error');
20196
20188
  })
20197
20189
  .finally(() => {
20198
20190
  delete card.dataset.remoteFleetOpeningControlNode;
@@ -21112,7 +21104,7 @@
21112
21104
  * ▼▼▼ [New] text/markdown 노드를 위한 동적 DOM 요소 생성 ▼▼▼
21113
21105
  * Blazor 템플릿이 없는 노드 타입을 위해 JS에서 직접 DOM 생성
21114
21106
  */
21115
- // Expose helper to create markdown content consistently
21107
+ // Expose helper to create markdown content consistently
21116
21108
  function createMarkdownContent(content, isCodeNode = false) {
21117
21109
  const wrapper = document.createElement('div');
21118
21110
  const normalizedContent = typeof content === 'string' ? content : String(content || '');
@@ -21169,22 +21161,22 @@
21169
21161
  contentEl.textContent = nextText;
21170
21162
  }
21171
21163
  }
21172
-
21173
- function getCachedMarkdownHtml(nodeModel, content) {
21174
- if (!nodeModel) return null;
21175
- if (nodeModel._cachedMarkdownSource === content && nodeModel._cachedMarkdownHtml) {
21176
- return nodeModel._cachedMarkdownHtml;
21177
- }
21178
- return null;
21179
- }
21180
-
21181
- function setCachedMarkdownHtml(nodeModel, content, html) {
21182
- if (!nodeModel) return;
21183
- nodeModel._cachedMarkdownSource = content;
21184
- nodeModel._cachedMarkdownHtml = html;
21185
- }
21186
-
21187
- // HTML 이스케이프 헬퍼 - kept for compatibility
21164
+
21165
+ function getCachedMarkdownHtml(nodeModel, content) {
21166
+ if (!nodeModel) return null;
21167
+ if (nodeModel._cachedMarkdownSource === content && nodeModel._cachedMarkdownHtml) {
21168
+ return nodeModel._cachedMarkdownHtml;
21169
+ }
21170
+ return null;
21171
+ }
21172
+
21173
+ function setCachedMarkdownHtml(nodeModel, content, html) {
21174
+ if (!nodeModel) return;
21175
+ nodeModel._cachedMarkdownSource = content;
21176
+ nodeModel._cachedMarkdownHtml = html;
21177
+ }
21178
+
21179
+ // HTML 이스케이프 헬퍼 - kept for compatibility
21188
21180
  function escapeHtml(text) {
21189
21181
  const div = document.createElement('div');
21190
21182
  div.textContent = text;
@@ -23193,7 +23185,7 @@
23193
23185
  ensurePromptCopyDelegation();
23194
23186
 
23195
23187
  // ... (logging)
23196
-
23188
+
23197
23189
  // 컨테이너 생성
23198
23190
  const container = document.createElement('div');
23199
23191
  container.id = `node-${nodeModel.id}`;
@@ -23217,7 +23209,7 @@
23217
23209
  color: #1f2937;
23218
23210
  box-shadow: none;
23219
23211
  `;
23220
-
23212
+
23221
23213
  // 콘텐츠 래퍼 생성
23222
23214
  const contentWrapper = document.createElement('div');
23223
23215
  contentWrapper.className = isCsvTableNode
@@ -23316,12 +23308,12 @@
23316
23308
  // 응답 영역 생성
23317
23309
  const responseDiv = document.createElement('div');
23318
23310
  responseDiv.id = `node-response-${nodeModel.id}`;
23319
-
23320
- // [FIX] 'note' type should also render markdown
23311
+
23312
+ // [FIX] 'note' type should also render markdown
23321
23313
  const isCodeNode = contentTypeLower === 'code';
23322
23314
  // Variable isNoteNode declared above
23323
23315
  const isMarkdownType = ['text', 'markdown'].includes(contentTypeLower); // Removed 'note'
23324
-
23316
+
23325
23317
  if (isCsvTableNode) {
23326
23318
  responseDiv.className = 'node-response csv-table-scroll';
23327
23319
  responseDiv.style.cssText = `
@@ -23387,44 +23379,44 @@
23387
23379
  padding-right: 14px;
23388
23380
  box-sizing: border-box;
23389
23381
  `;
23390
-
23391
- // 1. Save Metadata
23392
- responseDiv.dataset.src = content;
23393
- responseDiv.dataset.nodeId = nodeModel.id;
23394
-
23395
- // 2. Render Markdown or Text
23396
- const cachedHtml = getCachedMarkdownHtml(nodeModel, content);
23397
- if (cachedHtml) {
23398
- responseDiv.innerHTML = cachedHtml;
23399
- } else if (typeof marked !== 'undefined' && content) {
23400
- const options = { breaks: true, gfm: true };
23401
- try {
23402
- if (typeof marked.parse === 'function') {
23403
- responseDiv.innerHTML = marked.parse(content, options);
23404
- } else if (typeof marked === 'function') {
23405
- responseDiv.innerHTML = marked(content, options);
23406
- } else {
23407
- responseDiv.innerText = content;
23408
- }
23409
- setCachedMarkdownHtml(nodeModel, content, responseDiv.innerHTML);
23410
- } catch {
23411
- responseDiv.innerText = content;
23412
- }
23413
- } else {
23414
- responseDiv.innerText = content;
23415
- }
23416
-
23417
- // 3. (Removed) Click Event Listener
23418
- // Centralized interaction logic in mind-map-interactions.js handles selection and editing.
23419
- // Also pointer-events are none by default, so this wouldn't fire.
23420
-
23421
- // Prevent drag interference (optional, but harmless)
23422
- responseDiv.ondragstart = (e) => {
23423
- if (e.target.tagName !== 'A' && !e.target.closest('a')) {
23424
- e.preventDefault();
23425
- }
23426
- };
23427
-
23382
+
23383
+ // 1. Save Metadata
23384
+ responseDiv.dataset.src = content;
23385
+ responseDiv.dataset.nodeId = nodeModel.id;
23386
+
23387
+ // 2. Render Markdown or Text
23388
+ const cachedHtml = getCachedMarkdownHtml(nodeModel, content);
23389
+ if (cachedHtml) {
23390
+ responseDiv.innerHTML = cachedHtml;
23391
+ } else if (typeof marked !== 'undefined' && content) {
23392
+ const options = { breaks: true, gfm: true };
23393
+ try {
23394
+ if (typeof marked.parse === 'function') {
23395
+ responseDiv.innerHTML = marked.parse(content, options);
23396
+ } else if (typeof marked === 'function') {
23397
+ responseDiv.innerHTML = marked(content, options);
23398
+ } else {
23399
+ responseDiv.innerText = content;
23400
+ }
23401
+ setCachedMarkdownHtml(nodeModel, content, responseDiv.innerHTML);
23402
+ } catch {
23403
+ responseDiv.innerText = content;
23404
+ }
23405
+ } else {
23406
+ responseDiv.innerText = content;
23407
+ }
23408
+
23409
+ // 3. (Removed) Click Event Listener
23410
+ // Centralized interaction logic in mind-map-interactions.js handles selection and editing.
23411
+ // Also pointer-events are none by default, so this wouldn't fire.
23412
+
23413
+ // Prevent drag interference (optional, but harmless)
23414
+ responseDiv.ondragstart = (e) => {
23415
+ if (e.target.tagName !== 'A' && !e.target.closest('a')) {
23416
+ e.preventDefault();
23417
+ }
23418
+ };
23419
+
23428
23420
  } else if (isMarkdownType) {
23429
23421
  responseDiv.className = 'node-response markdown-body mind-map-text-scrollable-view';
23430
23422
  // ▼▼▼ [FIX] 텍스트 선택을 위한 스타일 설정 ▼▼▼
@@ -23442,15 +23434,15 @@
23442
23434
  user-select: text;
23443
23435
  -webkit-user-select: text;
23444
23436
  `;
23445
- // ▲▲▲ [FIX] ▲▲▲
23446
- const cachedHtml = getCachedMarkdownHtml(nodeModel, content);
23447
- if (cachedHtml) {
23448
- responseDiv.innerHTML = cachedHtml;
23449
- } else {
23450
- const html = createMarkdownContent(content);
23451
- responseDiv.innerHTML = html;
23452
- setCachedMarkdownHtml(nodeModel, content, html);
23453
- }
23437
+ // ▲▲▲ [FIX] ▲▲▲
23438
+ const cachedHtml = getCachedMarkdownHtml(nodeModel, content);
23439
+ if (cachedHtml) {
23440
+ responseDiv.innerHTML = cachedHtml;
23441
+ } else {
23442
+ const html = createMarkdownContent(content);
23443
+ responseDiv.innerHTML = html;
23444
+ setCachedMarkdownHtml(nodeModel, content, html);
23445
+ }
23454
23446
  } else if (contentTypeLower === 'image') {
23455
23447
  responseDiv.className = 'node-response image-content';
23456
23448
  responseDiv.style.cssText = `width: 100%; height: 100%; display: ${hasContent ? 'block' : 'none'}; position: relative; overflow: hidden; pointer-events: none; padding: 0 !important; margin: 0 !important; background: transparent; border: 0 !important; scrollbar-width: none;`;
@@ -23534,15 +23526,15 @@
23534
23526
 
23535
23527
  return container;
23536
23528
  }
23537
-
23538
- // HTML 이스케이프 헬퍼
23539
- function escapeHtml(text) {
23540
- const div = document.createElement('div');
23541
- div.textContent = text;
23542
- return div.innerHTML;
23543
- }
23544
- // ▲▲▲ [New] ▲▲▲
23545
-
23529
+
23530
+ // HTML 이스케이프 헬퍼
23531
+ function escapeHtml(text) {
23532
+ const div = document.createElement('div');
23533
+ div.textContent = text;
23534
+ return div.innerHTML;
23535
+ }
23536
+ // ▲▲▲ [New] ▲▲▲
23537
+
23546
23538
  function getNodeViewContentInfo(nodeModel) {
23547
23539
  const contentTypeLower = String(nodeModel?.contentType ?? nodeModel?.ContentType ?? '').toLowerCase();
23548
23540
  const remoteFleetMonitor = isRemoteFleetMonitorNode(nodeModel);
@@ -23800,7 +23792,7 @@
23800
23792
  console.error('[MindMapCss3DManager] CSS3DObject not available');
23801
23793
  return null;
23802
23794
  }
23803
-
23795
+
23804
23796
  const resolvedNodeId = getNodeId(nodeModel);
23805
23797
  const templateId = `node-${resolvedNodeId}`;
23806
23798
  let templateElement = document.getElementById(templateId);
@@ -23822,60 +23814,60 @@
23822
23814
  || contentTypeLower === 'embed') {
23823
23815
  templateElement = null;
23824
23816
  }
23825
-
23826
- let isDynamicallyCreated = false;
23827
- // text/markdown 타입은 Blazor 템플릿이 없으므로 동적 생성
23828
- if (!templateElement) {
23829
- // text/markdown type has no Blazor template, so create dynamically.
23830
- // [FIX] 'note' type also falls back to dynamic creation if not found (or if we force dynamic for consistent MD)
23831
- // But usually 'note' comes from Razor. If missing, we create it.
23817
+
23818
+ let isDynamicallyCreated = false;
23819
+ // text/markdown 타입은 Blazor 템플릿이 없으므로 동적 생성
23820
+ if (!templateElement) {
23821
+ // text/markdown type has no Blazor template, so create dynamically.
23822
+ // [FIX] 'note' type also falls back to dynamic creation if not found (or if we force dynamic for consistent MD)
23823
+ // But usually 'note' comes from Razor. If missing, we create it.
23832
23824
  const dynamicTypes = ['text', 'markdown', 'code', 'note', 'memo', CSV_TABLE_CONTENT_TYPE, 'templatelauncher', 'image', 'video', 'embed'];
23833
23825
 
23834
23826
  if (remoteFleetMonitor || dynamicTypes.includes(contentTypeLower)) {
23835
23827
  log(`[MindMapCss3DManager] Deferring dynamic DOM element for ${visualContentTypeLower || contentTypeLower} node ${resolvedNodeId} to shared NodeView`);
23836
23828
  templateElement = document.createElement('div');
23837
23829
  isDynamicallyCreated = true;
23838
- } else {
23839
- // 경고 로그 제한 (초기 로딩 시 템플릿이 아직 렌더링되지 않은 경우 스팸 방지)
23840
- if (!window._templateWarningCount) window._templateWarningCount = 0;
23841
- if (window._templateWarningCount < 5) {
23830
+ } else {
23831
+ // 경고 로그 제한 (초기 로딩 시 템플릿이 아직 렌더링되지 않은 경우 스팸 방지)
23832
+ if (!window._templateWarningCount) window._templateWarningCount = 0;
23833
+ if (window._templateWarningCount < 5) {
23842
23834
  warn(`[MindMapCss3DManager] Template not found for node ${resolvedNodeId} (async templates pending). Template ID: ${templateId}`);
23843
- window._templateWarningCount++;
23844
- if (window._templateWarningCount === 5) {
23845
- warn(`[MindMapCss3DManager] ... suppressing further template warnings. Templates will be available after async render.`);
23846
- }
23847
- }
23848
- return null;
23849
- }
23850
- }
23851
- // ▲▲▲ [New] ▲▲▲
23852
-
23835
+ window._templateWarningCount++;
23836
+ if (window._templateWarningCount === 5) {
23837
+ warn(`[MindMapCss3DManager] ... suppressing further template warnings. Templates will be available after async render.`);
23838
+ }
23839
+ }
23840
+ return null;
23841
+ }
23842
+ }
23843
+ // ▲▲▲ [New] ▲▲▲
23844
+
23853
23845
  const existingCss3dId = `css3d-node-${resolvedNodeId}`;
23854
23846
  const nodeEntry = module.nodeObjectsById.get(resolvedNodeId);
23855
-
23856
- if (nodeEntry && nodeEntry.cssObject) {
23847
+
23848
+ if (nodeEntry && nodeEntry.cssObject) {
23857
23849
  warn(`[MindMapCss3DManager] CSS3D object already exists in memory for node ${resolvedNodeId}. Returning existing object.`);
23858
- return nodeEntry.cssObject;
23859
- }
23860
-
23861
- const existingCss3dElement = document.getElementById(existingCss3dId);
23862
- if (existingCss3dElement) {
23863
- warn(`[MindMapCss3DManager] Removing duplicate CSS3D element from DOM: ${existingCss3dId}`);
23864
- if (existingCss3dElement.parentElement) {
23865
- existingCss3dElement.parentElement.removeChild(existingCss3dElement);
23866
- }
23867
- }
23868
-
23869
- // (It lives in a hidden div, so it is not visible.)
23870
- /*
23871
- const razorMapNode = document.querySelector(`#${templateId}`);
23872
- if (razorMapNode && razorMapNode.parentElement) {
23873
- console.warn(`[MindMapCss3DManager] Removing Razor-generated MapNode from DOM: ${templateId}`);
23874
- razorMapNode.parentElement.removeChild(razorMapNode);
23875
- }
23876
- */
23877
- // ▲▲▲ [Critical fix] ▲▲▲
23878
-
23850
+ return nodeEntry.cssObject;
23851
+ }
23852
+
23853
+ const existingCss3dElement = document.getElementById(existingCss3dId);
23854
+ if (existingCss3dElement) {
23855
+ warn(`[MindMapCss3DManager] Removing duplicate CSS3D element from DOM: ${existingCss3dId}`);
23856
+ if (existingCss3dElement.parentElement) {
23857
+ existingCss3dElement.parentElement.removeChild(existingCss3dElement);
23858
+ }
23859
+ }
23860
+
23861
+ // (It lives in a hidden div, so it is not visible.)
23862
+ /*
23863
+ const razorMapNode = document.querySelector(`#${templateId}`);
23864
+ if (razorMapNode && razorMapNode.parentElement) {
23865
+ console.warn(`[MindMapCss3DManager] Removing Razor-generated MapNode from DOM: ${templateId}`);
23866
+ razorMapNode.parentElement.removeChild(razorMapNode);
23867
+ }
23868
+ */
23869
+ // ▲▲▲ [Critical fix] ▲▲▲
23870
+
23879
23871
  const nodeView = createNodeViewElement(module, nodeModel, {
23880
23872
  hostKind: NODE_VIEW_HOST_CSS3D,
23881
23873
  idPrefix: 'css3d-node'
@@ -23900,31 +23892,31 @@
23900
23892
  }
23901
23893
  clonedElement.style.display = '';
23902
23894
  clonedElement.style.transformOrigin = '0% 0%';
23903
-
23904
- // ▼▼▼ [Clarity] Scale factor for high-resolution rendering ▼▼▼
23895
+
23896
+ // ▼▼▼ [Clarity] Scale factor for high-resolution rendering ▼▼▼
23905
23897
  const resolutionScale = nodeView.resolutionScale;
23906
23898
  applyCss3dResolutionLayout(clonedElement, width, height, resolutionScale, {
23907
23899
  resetLayoutTransform: true
23908
23900
  });
23909
23901
  clonedElement.style.borderRadius = '0px';
23910
-
23911
- // to the inner element, the node shifts down-right.
23912
- // Remove it so the element sits at the wrapper origin (0,0).
23913
- clonedElement.style.position = 'absolute';
23914
- clonedElement.style.left = '0px';
23915
- clonedElement.style.top = '0px';
23916
- // ▲▲▲ [Changed] ▲▲▲
23917
-
23902
+
23903
+ // to the inner element, the node shifts down-right.
23904
+ // Remove it so the element sits at the wrapper origin (0,0).
23905
+ clonedElement.style.position = 'absolute';
23906
+ clonedElement.style.left = '0px';
23907
+ clonedElement.style.top = '0px';
23908
+ // ▲▲▲ [Changed] ▲▲▲
23909
+
23918
23910
  clonedElement.style.transition = 'none';
23919
23911
  clonedElement.style.webkitTransition = 'none';
23920
-
23921
- // ▼▼▼ [Perf] GPU acceleration for smooth zoom/pan ▼▼▼
23922
- // 초기에는 꺼둠 (auto). 카메라 이동 시 mind-map-core.js가 setWillChange(true) 호출
23912
+
23913
+ // ▼▼▼ [Perf] GPU acceleration for smooth zoom/pan ▼▼▼
23914
+ // 초기에는 꺼둠 (auto). 카메라 이동 시 mind-map-core.js가 setWillChange(true) 호출
23923
23915
  clonedElement.style.willChange = 'auto'; // 'transform';
23924
23916
  clonedElement.style.backfaceVisibility = isMediaNode ? 'visible' : 'hidden';
23925
23917
  clonedElement.style.webkitBackfaceVisibility = isMediaNode ? 'visible' : 'hidden';
23926
23918
  // ▲▲▲ [Perf] ▲▲▲
23927
-
23919
+
23928
23920
  const wrapper = document.createElement('div');
23929
23921
  const wrapperTypeClass = `node-type-${visualContentTypeLower || 'unknown'}`;
23930
23922
  wrapper.className = `css3d-resolution-wrapper lod-high ${wrapperTypeClass}`;
@@ -23937,10 +23929,10 @@
23937
23929
  wrapper.dataset.nodeId = resolvedNodeId;
23938
23930
  wrapper.style.transition = CSS3D_WRAPPER_TRANSITION;
23939
23931
  wrapper.style.webkitTransition = CSS3D_WRAPPER_WEBKIT_TRANSITION;
23940
-
23941
- wrapper.style.transformOrigin = '0 0';
23942
- // ▲▲▲ [Critical fix] ▲▲▲
23943
-
23932
+
23933
+ wrapper.style.transformOrigin = '0 0';
23934
+ // ▲▲▲ [Critical fix] ▲▲▲
23935
+
23944
23936
  // ▼▼▼ [Fix] 외부 스크롤바 방지 - 내부 콘텐츠 스크롤바만 표시 ▼▼▼
23945
23937
  wrapper.style.overflow = allowsExternalNodeChrome ? 'visible' : 'hidden';
23946
23938
  if (allowsExternalNodeChrome) {
@@ -23949,14 +23941,14 @@
23949
23941
  wrapper.style.containIntrinsicSize = 'auto';
23950
23942
  }
23951
23943
  // ▲▲▲ [Fix] ▲▲▲
23952
-
23953
- // ▼▼▼ [Perf] GPU acceleration for smooth zoom/pan ▼▼▼
23944
+
23945
+ // ▼▼▼ [Perf] GPU acceleration for smooth zoom/pan ▼▼▼
23954
23946
  wrapper.style.willChange = 'auto'; // 'transform';
23955
23947
  wrapper.style.backfaceVisibility = isMediaNode ? 'visible' : 'hidden';
23956
23948
  wrapper.style.webkitBackfaceVisibility = isMediaNode ? 'visible' : 'hidden';
23957
23949
  wrapper.style.transformStyle = isMediaNode ? 'flat' : '';
23958
23950
  // ▲▲▲ [Perf] ▲▲▲
23959
-
23951
+
23960
23952
  if (contentTypeLower === 'image' || contentTypeLower === 'video') {
23961
23953
  wrapper.style.pointerEvents = 'auto'; // [Fix] Changed from none to auto for resize handle detection
23962
23954
  clonedElement.style.pointerEvents = 'none';
@@ -24033,10 +24025,10 @@
24033
24025
  }
24034
24026
 
24035
24027
  // ... (event listeners) ...
24036
-
24037
- const css3dObj = new CSS3DObjectCtor(wrapper);
24038
- css3dObj.scale.set(1 / resolutionScale, 1 / resolutionScale, 1 / resolutionScale);
24039
- css3dObj.visible = false;
24028
+
24029
+ const css3dObj = new CSS3DObjectCtor(wrapper);
24030
+ css3dObj.scale.set(1 / resolutionScale, 1 / resolutionScale, 1 / resolutionScale);
24031
+ css3dObj.visible = false;
24040
24032
  css3dObj.userData.nodeId = resolvedNodeId;
24041
24033
  css3dObj.userData.worldWidth = width;
24042
24034
  css3dObj.userData.worldHeight = height;
@@ -24046,10 +24038,10 @@
24046
24038
  }
24047
24039
 
24048
24040
  // ▼▼▼ [Fix] nodeModel의 위치 정보를 사용하여 초기 위치 설정 ▼▼▼
24049
- // 호출자가 위치를 따로 설정하지 않아도 올바른 위치에 생성됩니다.
24050
- // Z 오프셋 0: 그리드(Z=0)와 동일한 평면에 배치하여 원근/시차 완전 제거
24051
- const posX = nodeModel.positionX ?? nodeModel.PositionX ?? 0;
24052
- const posY = nodeModel.positionY ?? nodeModel.PositionY ?? 0;
24041
+ // 호출자가 위치를 따로 설정하지 않아도 올바른 위치에 생성됩니다.
24042
+ // Z 오프셋 0: 그리드(Z=0)와 동일한 평면에 배치하여 원근/시차 완전 제거
24043
+ const posX = nodeModel.positionX ?? nodeModel.PositionX ?? 0;
24044
+ const posY = nodeModel.positionY ?? nodeModel.PositionY ?? 0;
24053
24045
  const posZ = (nodeModel.positionZ ?? nodeModel.PositionZ ?? 0); // [Fix] Z Offset 제거 (0으로 통일)
24054
24046
  css3dObj.position.set(posX, posY, posZ);
24055
24047
  // ▲▲▲ [Fix] ▲▲▲
@@ -24062,7 +24054,7 @@
24062
24054
 
24063
24055
  return css3dObj;
24064
24056
  }
24065
-
24057
+
24066
24058
  function syncCss3dContent(nodeModel, cssObject) {
24067
24059
  if (!nodeModel || !cssObject || !cssObject.element) return;
24068
24060
  const nodeId = String(getNodeId(nodeModel) || '').trim();
@@ -24147,7 +24139,7 @@
24147
24139
  });
24148
24140
  }
24149
24141
  }
24150
-
24142
+
24151
24143
  const prompt = nodeModel.prompt ?? nodeModel.Prompt ?? '';
24152
24144
  if (contentTypeLower === 'image' || contentTypeLower === 'video' || contentTypeLower === 'embed') {
24153
24145
  restoreCss3dMediaOverlayState(nodeModel, cssObject);
@@ -24506,19 +24498,19 @@
24506
24498
  renderBusinessAutomationChrome(resolveBusinessAutomationChromeContainer(el), nodeModel);
24507
24499
  }
24508
24500
  // Reusable objects for Frustum Culling (avoid GC)
24509
- const _frustum = new THREE.Frustum();
24510
- const _projScreenMatrix = new THREE.Matrix4();
24511
- const _worldPos = new THREE.Vector3();
24512
- const _worldQuat = new THREE.Quaternion();
24513
- const _worldScale = new THREE.Vector3();
24514
- const _nodeBox = new THREE.Box3(); // 바운딩 박스용
24515
-
24516
-
24517
- // ▼▼▼ [New] Minimal update loop - only syncs positions when dirty ▼▼▼
24518
- // 그리드와 동일한 Z 평면(Z=0)에 배치하여 원근/시차 완전 제거
24519
- // [Fix] CSS3D 노드도 Z=0으로 완전 평탄화하여 카메라 이동 시 그리드와 정렬 유지
24520
- const CSS3D_Z_OFFSET = 0;
24521
-
24501
+ const _frustum = new THREE.Frustum();
24502
+ const _projScreenMatrix = new THREE.Matrix4();
24503
+ const _worldPos = new THREE.Vector3();
24504
+ const _worldQuat = new THREE.Quaternion();
24505
+ const _worldScale = new THREE.Vector3();
24506
+ const _nodeBox = new THREE.Box3(); // 바운딩 박스용
24507
+
24508
+
24509
+ // ▼▼▼ [New] Minimal update loop - only syncs positions when dirty ▼▼▼
24510
+ // 그리드와 동일한 Z 평면(Z=0)에 배치하여 원근/시차 완전 제거
24511
+ // [Fix] CSS3D 노드도 Z=0으로 완전 평탄화하여 카메라 이동 시 그리드와 정렬 유지
24512
+ const CSS3D_Z_OFFSET = 0;
24513
+
24522
24514
  function syncCssPositionFromGl(module, nodeEntry, targetZ) {
24523
24515
  const glObject = nodeEntry.glObject;
24524
24516
  const cssPos = nodeEntry.cssObject.position;
@@ -24622,8 +24614,8 @@
24622
24614
  dirtyIds?.clear?.();
24623
24615
  module._cssParentRepairNeeded = false;
24624
24616
  }
24625
- // ▲▲▲ [New] ▲▲▲
24626
-
24617
+ // ▲▲▲ [New] ▲▲▲
24618
+
24627
24619
  // ▼▼▼ [New] 일괄 will-change 제어 (성능 최적화) ▼▼▼
24628
24620
  let _willChangeState = null; // null = unknown, true = enabled, false = disabled
24629
24621
  let _willChangeTouchedIds = new Set();
@@ -24818,10 +24810,10 @@
24818
24810
  }
24819
24811
  if (el.style.willChange !== value) el.style.willChange = value;
24820
24812
  });
24821
-
24822
- // Razor 템플릿 기반 요소 (id가 css3d-node-로 시작하는 것들)
24823
- // (위의 createCss3dObject에서 clonedElement에 해당)
24824
- // querySelectorAll로 잡을 수 있음
24813
+
24814
+ // Razor 템플릿 기반 요소 (id가 css3d-node-로 시작하는 것들)
24815
+ // (위의 createCss3dObject에서 clonedElement에 해당)
24816
+ // querySelectorAll로 잡을 수 있음
24825
24817
  const clonedElements = document.querySelectorAll('[id^="css3d-node-"]');
24826
24818
  clonedElements.forEach(el => {
24827
24819
  if (effectiveEnabled && shouldPreserveAgentMotionCssElement(el)) {
@@ -24831,9 +24823,9 @@
24831
24823
  if (el.style.willChange !== value) el.style.willChange = value;
24832
24824
  });
24833
24825
  }
24834
- // ▲▲▲ [New] ▲▲▲
24835
-
24836
- // Expose MindMapCss3DManager globally
24826
+ // ▲▲▲ [New] ▲▲▲
24827
+
24828
+ // Expose MindMapCss3DManager globally
24837
24829
  window.MindMapCss3DManager = {
24838
24830
  activateSelectableTextOverlay: activateSelectableTextOverlay,
24839
24831
  activateNativeTextSelectionSource: activateNativeTextSelectionSource,
@@ -24912,6 +24904,6 @@
24912
24904
  hideBusinessAutomationFloatingTooltipForNode: hideBusinessAutomationFloatingTooltipForNode,
24913
24905
  update: update // Export
24914
24906
  };
24915
-
24916
- console.log('✓ mind-map-css3d-manager.js loaded (Note editing fixed)');
24917
- })();
24907
+
24908
+ console.log('✓ mind-map-css3d-manager.js loaded (Note editing fixed)');
24909
+ })();