@mindexec/cli 0.2.25 → 0.2.27

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.25",
3
+ "version": "0.2.27",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -20,11 +20,11 @@
20
20
  "scripts": {
21
21
  "start": "node launch-bridge.cjs",
22
22
  "dev": "node launch-bridge.cjs --watch",
23
- "test:syntax": "node --check server.js && node --check remote-hub.js && node --check codex-runtime.js && node --check launch-bridge.cjs && node --check port-guard.cjs && node --check scripts/setup-tree-sitter-grammars.mjs && node --check scripts/remote-hub-smoke.mjs && node --check scripts/remote-hub-scale-smoke.mjs && node --check scripts/remote-fleet-render-smoke.mjs && node --check scripts/remote-http-smoke.mjs",
24
- "test:remote": "node scripts/remote-hub-smoke.mjs",
25
- "test:remote:scale": "node scripts/remote-hub-scale-smoke.mjs",
26
- "test:remote:render": "node scripts/remote-fleet-render-smoke.mjs",
27
- "test:remote:http": "node scripts/remote-http-smoke.mjs",
23
+ "test:syntax": "node --check server.js && node --check remote-hub.js && node --check codex-runtime.js && node --check launch-bridge.cjs && node --check port-guard.cjs && node --check scripts/setup-tree-sitter-grammars.mjs && node --check scripts/remote-hub-smoke.mjs && node --check scripts/remote-hub-scale-smoke.mjs && node --check scripts/remote-fleet-render-smoke.mjs && node --check scripts/remote-http-smoke.mjs",
24
+ "test:remote": "node scripts/remote-hub-smoke.mjs",
25
+ "test:remote:scale": "node scripts/remote-hub-scale-smoke.mjs",
26
+ "test:remote:render": "node scripts/remote-fleet-render-smoke.mjs",
27
+ "test:remote:http": "node scripts/remote-http-smoke.mjs",
28
28
  "pack:dry": "npm pack --dry-run",
29
29
  "setup:grammars": "node scripts/setup-tree-sitter-grammars.mjs",
30
30
  "postinstall": "npm run setup:grammars"
@@ -557,7 +557,6 @@ try {
557
557
  const rawDevices = hub.listDevices();
558
558
  const devices = rawDevices.map(projectDevice);
559
559
  const connectedCount = devices.filter(device => device.Connected).length;
560
- const offlineCount = devices.length - connectedCount;
561
560
  const aiCount = devices.filter(device => device.Connected && device.AiAssistEnabled).length;
562
561
  const focusedDevice = devices.find(device => device.Connected && device.LiveStreamActive)
563
562
  || devices.find(device => device.Connected);
@@ -667,16 +666,34 @@ try {
667
666
  };
668
667
  manager.setModuleForTest({ dotNetHelper });
669
668
 
669
+ const nodeShell = document.createElement('div');
670
+ nodeShell.setAttribute('class', 'map-node-remote-fleet map-node-memo');
671
+ const header = document.createElement('div');
672
+ header.setAttribute('class', 'map-node-memo__header');
673
+ const iconWrap = document.createElement('div');
674
+ iconWrap.setAttribute('class', 'map-node-memo__icon-wrap');
675
+ const iconButton = document.createElement('button');
676
+ iconButton.setAttribute('class', 'map-node-memo__icon-button');
677
+ iconWrap.appendChild(iconButton);
678
+ const titleWrap = document.createElement('div');
679
+ titleWrap.setAttribute('class', 'map-node-memo__title-wrap');
680
+ titleWrap.textContent = 'Remote Fleet Monitor';
681
+ header.appendChild(iconWrap);
682
+ header.appendChild(titleWrap);
670
683
  const bodyView = document.createElement('div');
671
684
  bodyView.dataset.remoteFleetAutoMonitor = 'false';
672
685
  bodyView.dataset.remoteFleetFocusDeviceId = focusedDevice.DeviceId;
673
- document.body.appendChild(bodyView);
686
+ nodeShell.appendChild(header);
687
+ nodeShell.appendChild(bodyView);
688
+ document.body.appendChild(nodeShell);
674
689
 
675
690
  manager.renderRemoteFleetMonitorForTest(bodyView, buildMonitorNode(devices, hub.getStatus({ includeSecrets: true }), latestTaskBatch, recentTaskBatches));
676
691
 
677
692
  let cards = bodyView.querySelectorAll('article[data-device-id]');
678
693
  assert.equal(cards.length, SYNTHETIC_COUNT);
679
- assert.equal(bodyView.querySelector('[data-remote-fleet-match-count="true"]')?.textContent, `${SYNTHETIC_COUNT}/${SYNTHETIC_COUNT}`);
694
+ assert.equal(bodyView.querySelector('[data-remote-fleet-match-count="true"]'), null);
695
+ assert.equal(bodyView.querySelector('[data-remote-fleet-search="true"]'), null);
696
+ assert.equal(bodyView.querySelectorAll('select').length, 0);
680
697
  assert.ok(bodyView.querySelector('[data-remote-fleet-device-grid="true"]'));
681
698
  const initialDetailPanel = bodyView.querySelector('[data-remote-fleet-detail-panel="true"]');
682
699
  assert.equal(initialDetailPanel?.dataset.deviceId, focusedDevice.DeviceId);
@@ -702,9 +719,12 @@ try {
702
719
  assert.equal(bodyView.querySelector('[data-remote-fleet-action="task-connected"]'), null);
703
720
  assert.equal(bodyView.querySelector('[data-remote-fleet-task-input="true"]'), null);
704
721
  assert.equal(bodyView.querySelector('[data-remote-fleet-ai-toggle="true"]'), null);
705
- assert.ok(bodyView.textContent.includes('all devices, no paging'));
706
- assert.equal(bodyView.querySelector('[data-remote-fleet-host-state="true"]')?.textContent.includes('Host: Inactive'), true);
707
- const initialHostButton = bodyView.querySelector('[data-remote-fleet-action="set-host"]');
722
+ assert.equal(bodyView.textContent.includes('all devices, no paging'), false);
723
+ assert.equal(bodyView.textContent.includes('Host: Inactive'), false);
724
+ assert.equal(bodyView.textContent.includes('No screen'), false);
725
+ assert.equal(bodyView.textContent.includes('@mindexec/cli'), false);
726
+ assert.equal(nodeShell.querySelector('[data-remote-fleet-host-indicator="true"]')?.dataset.remoteFleetHostActive, 'false');
727
+ const initialHostButton = nodeShell.querySelector('[data-remote-fleet-action="set-host"]');
708
728
  assert.equal(initialHostButton?.textContent, 'Set Host');
709
729
  initialHostButton.dispatchEvent({ type: 'click' });
710
730
  await wait();
@@ -719,9 +739,10 @@ try {
719
739
  leaseMs: 30000
720
740
  });
721
741
  manager.renderRemoteFleetMonitorForTest(bodyView, buildMonitorNode(devices, hub.getStatus({ includeSecrets: true }), latestTaskBatch, recentTaskBatches));
722
- assert.equal(bodyView.querySelector('[data-remote-fleet-host-state="true"]')?.textContent.includes('Host: Active'), true);
723
- assert.equal(bodyView.querySelector('[data-remote-fleet-action="set-host"]')?.textContent, 'Hosting');
724
- const stopHostButton = bodyView.querySelector('[data-remote-fleet-action="stop-host"]');
742
+ assert.equal(bodyView.textContent.includes('Host: Active'), false);
743
+ assert.equal(nodeShell.querySelector('[data-remote-fleet-host-indicator="true"]')?.dataset.remoteFleetHostActive, 'true');
744
+ assert.equal(nodeShell.querySelector('[data-remote-fleet-action="set-host"]')?.textContent, 'Set Host');
745
+ const stopHostButton = nodeShell.querySelector('[data-remote-fleet-action="stop-host"]');
725
746
  assert.ok(stopHostButton);
726
747
  const stopHostStart = dotNetCalls.length;
727
748
  stopHostButton.dispatchEvent({ type: 'click' });
@@ -733,7 +754,6 @@ try {
733
754
  assert.equal(stopHostCall.args[0], 'remote-fleet-render-smoke');
734
755
  assert.equal(stopHostCall.args[1], false);
735
756
 
736
- assert.match(bodyView.querySelector('[data-remote-fleet-manager-version="true"]')?.textContent || '', /^@mindexec\/cli render-smoke-manager - 127\.0\.0\.1:\d+$/);
737
757
  assert.ok(livePanel?.textContent.includes('RemoteFast 20 fps'), livePanel?.textContent || bodyView.textContent);
738
758
  assert.equal(bodyView.querySelector('code'), null);
739
759
  assert.equal(bodyView.textContent.includes('npx @mindexec/remote connect'), false);
@@ -761,49 +781,31 @@ try {
761
781
  assert.equal(bodyView.dataset.remoteFleetTaskFollowKey, 'render-smoke-batch');
762
782
  assert.ok(Number(bodyView.dataset.remoteFleetTaskFollowTicks || '0') >= 1);
763
783
 
764
- const searchInput = bodyView.querySelector('[data-remote-fleet-search="true"]');
765
- searchInput.value = 'synthetic pc 0001';
766
- searchInput.dispatchEvent({ type: 'input' });
767
- cards = bodyView.querySelectorAll('article[data-device-id]');
768
- assert.equal(cards.filter(card => card.style.display !== 'none').length, 1);
769
- assert.equal(bodyView.querySelector('[data-remote-fleet-match-count="true"]')?.textContent, `1/${SYNTHETIC_COUNT}`);
770
-
771
- const selects = bodyView.querySelectorAll('select');
772
- assert.equal(selects.length, 4);
773
- const [filterSelect, , groupSelect] = selects;
774
- searchInput.value = '';
775
- searchInput.dispatchEvent({ type: 'input' });
776
- filterSelect.value = 'offline';
777
- filterSelect.dispatchEvent({ type: 'change' });
778
- assert.equal(bodyView.querySelector('[data-remote-fleet-match-count="true"]')?.textContent, `${offlineCount}/${SYNTHETIC_COUNT}`);
779
-
780
- filterSelect.value = 'ai';
781
- filterSelect.dispatchEvent({ type: 'change' });
782
- assert.equal(bodyView.querySelector('[data-remote-fleet-match-count="true"]')?.textContent, `${aiCount}/${SYNTHETIC_COUNT}`);
783
-
784
- filterSelect.value = 'all';
785
- filterSelect.dispatchEvent({ type: 'change' });
786
- groupSelect.value = 'status';
787
- groupSelect.dispatchEvent({ type: 'change' });
788
- assert.equal(bodyView.dataset.remoteFleetGroup, 'status');
789
- assert.ok(bodyView.querySelectorAll('[data-remote-fleet-group-header="true"]').length >= 2);
790
- assert.equal(bodyView.querySelectorAll('article[data-device-id]').length, SYNTHETIC_COUNT);
791
-
792
784
  const feedback = bodyView.querySelector('[data-remote-fleet-task-feedback="true"]');
793
785
  assert.ok(feedback);
794
- assert.equal(feedback.style.display, 'none');
795
786
 
796
- const activeSelects = bodyView.querySelectorAll('select');
797
- assert.equal(activeSelects.length, 4);
798
- const [aiFilterSelect] = activeSelects;
799
787
  assert.equal(bodyView.querySelector('[data-remote-fleet-ai-toggle="true"]'), null);
800
- aiFilterSelect.value = 'ai';
801
- aiFilterSelect.dispatchEvent({ type: 'change' });
802
- assert.equal(bodyView.querySelector('[data-remote-fleet-match-count="true"]')?.textContent, `${aiCount}/${SYNTHETIC_COUNT}`);
803
788
  assert.equal(dotNetCalls.some(call =>
804
789
  call.methodName === 'DispatchRemoteFleetTaskBatchFromJs'
805
790
  || call.methodName === 'DispatchRemoteFleetTaskFromJs'), false);
806
791
 
792
+ const emptyShell = document.createElement('div');
793
+ emptyShell.setAttribute('class', 'map-node-remote-fleet map-node-memo');
794
+ const emptyHeader = document.createElement('div');
795
+ emptyHeader.setAttribute('class', 'map-node-memo__header');
796
+ emptyHeader.appendChild(document.createElement('div'));
797
+ emptyHeader.appendChild(document.createElement('div'));
798
+ const emptyBody = document.createElement('div');
799
+ emptyShell.appendChild(emptyHeader);
800
+ emptyShell.appendChild(emptyBody);
801
+ document.body.appendChild(emptyShell);
802
+ manager.renderRemoteFleetMonitorForTest(emptyBody, buildMonitorNode([], hub.getStatus({ includeSecrets: true })));
803
+ assert.equal(emptyBody.querySelectorAll('[data-remote-fleet-empty-screen="true"]').length, 6);
804
+ assert.equal(emptyBody.textContent.includes('No devices connected yet.'), false);
805
+ assert.equal(emptyBody.textContent.includes('Select a screen'), false);
806
+ assert.equal(emptyBody.textContent.includes('No screen'), false);
807
+ assert.equal(emptyBody.textContent.includes('Endpoint 127.0.0.1'), false);
808
+
807
809
  const deviceBody = document.createElement('div');
808
810
  document.body.appendChild(deviceBody);
809
811
  manager.renderRemoteFleetDeviceForTest(deviceBody, buildDeviceNode(focusedDevice, hub.getStatus()));
@@ -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 = '20260610-template-board-ready-v205';
8
+ const MINDMAP_CORE_BUILD_ID = '20260613-cursor-blink-v206';
9
9
  const CanvasPhase = Object.freeze({
10
10
  Booting: 'booting',
11
11
  BoardFileLoading: 'board-file-loading',
@@ -83,6 +83,7 @@
83
83
  const PASSIVE_OVERLAY_INTERACTIVE_DENSE_VISIBLE_THRESHOLD = 40;
84
84
  const PASSIVE_OVERLAY_IDLE_DELAY_MS = 500;
85
85
  const PASSIVE_OVERLAY_PAN_IDLE_DELAY_MS = 500;
86
+ const CURSOR_BLINK_INTERVAL_MS = 500;
86
87
  const HOME_CAMERA_NORMAL_MID_Z = 8250;
87
88
  const HOME_DENSE_CLUSTER_MIN_CELL_SIZE = 1200;
88
89
  const HOME_DENSE_CLUSTER_MAX_CELL_SIZE = 3600;
@@ -2497,6 +2498,8 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
2497
2498
  this.cursorMesh = null;
2498
2499
  this.cursorMaterial = null;
2499
2500
  this.cursorLineMaterial = null;
2501
+ this._cursorBlinkVisible = true;
2502
+ this._lastCursorBlinkAt = 0;
2500
2503
  this._viewStatePersistTimer = null;
2501
2504
  this._pendingViewStateSnapshot = null;
2502
2505
  this._pendingViewStateKey = null;
@@ -5062,21 +5065,37 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5062
5065
  // 5. Cursor Animation
5063
5066
  // ----------------------------------------------------------------
5064
5067
  let cursorAnimatedThisFrame = false;
5065
- const shouldAnimateCursor =
5066
- !!this.cursorMaterial &&
5067
- (
5068
- (isContinuousTheme === true && allowContinuousThemeAnimationThisFrame === true) ||
5069
- isInteractiveFrame === true ||
5070
- shouldUpdate === true ||
5071
- hasForcedUpdate === true ||
5072
- isBoardLoading === true
5073
- );
5074
- if (shouldAnimateCursor) {
5075
- const time = Date.now() * 0.005;
5076
- const val = (Math.sin(time) * 0.5) + 0.5;
5077
- this.cursorMaterial.color.setRGB(val, val, val);
5078
- if (this.cursorLineMaterial) this.cursorLineMaterial.color.setRGB(val, val, val);
5079
- cursorAnimatedThisFrame = true;
5068
+ let cursorBlinkWakeDelayMs = Number.POSITIVE_INFINITY;
5069
+ const hasCursorBlinkTarget = !!(this.cursorMesh && this.cursorMaterial);
5070
+ if (hasCursorBlinkTarget) {
5071
+ if (this.cursorMesh.visible !== (this._cursorBlinkVisible === true)) {
5072
+ this.cursorMesh.visible = this._cursorBlinkVisible === true;
5073
+ cursorAnimatedThisFrame = true;
5074
+ }
5075
+
5076
+ const previousBlinkAt = Number(this._lastCursorBlinkAt || 0);
5077
+ if (!Number.isFinite(previousBlinkAt) || previousBlinkAt <= 0) {
5078
+ this._lastCursorBlinkAt = frameStart;
5079
+ this._cursorBlinkVisible = true;
5080
+ if (this.cursorMesh.visible !== true) {
5081
+ this.cursorMesh.visible = true;
5082
+ cursorAnimatedThisFrame = true;
5083
+ }
5084
+ } else {
5085
+ const elapsedBlinkMs = Math.max(0, frameStart - previousBlinkAt);
5086
+ if (elapsedBlinkMs >= CURSOR_BLINK_INTERVAL_MS) {
5087
+ const elapsedBlinkSteps = Math.max(1, Math.floor(elapsedBlinkMs / CURSOR_BLINK_INTERVAL_MS));
5088
+ if ((elapsedBlinkSteps % 2) === 1) {
5089
+ this._cursorBlinkVisible = this._cursorBlinkVisible !== true;
5090
+ }
5091
+ this._lastCursorBlinkAt = previousBlinkAt + (elapsedBlinkSteps * CURSOR_BLINK_INTERVAL_MS);
5092
+ this.cursorMesh.visible = this._cursorBlinkVisible === true;
5093
+ cursorAnimatedThisFrame = true;
5094
+ }
5095
+ }
5096
+
5097
+ const nextBlinkInMs = CURSOR_BLINK_INTERVAL_MS - Math.max(0, frameStart - Number(this._lastCursorBlinkAt || frameStart));
5098
+ cursorBlinkWakeDelayMs = Math.max(1, Math.min(CURSOR_BLINK_INTERVAL_MS, Math.ceil(nextBlinkInMs)));
5080
5099
  }
5081
5100
 
5082
5101
  // 6. Theme Animation
@@ -5475,12 +5494,32 @@ ${summaryLines.map(line => `<div>${escapeNodeFrameDebugHtml(line)}</div>`).join(
5475
5494
  shouldSyncTextOverlays !== true &&
5476
5495
  hasPendingAnimationWakeWork !== true;
5477
5496
  if (shouldParkIdleAnimationLoop) {
5497
+ if (hasCursorBlinkTarget) {
5498
+ nextAnimationLoopDelayMs = cursorBlinkWakeDelayMs;
5499
+ this._lastAnimationLoopDelayMs = nextAnimationLoopDelayMs;
5500
+ this._animationLoopExecuting = false;
5501
+ this._scheduleAnimationLoop(loop, nextAnimationLoopDelayMs);
5502
+ return;
5503
+ }
5504
+
5478
5505
  this._lastAnimationLoopDelayMs = -1;
5479
5506
  this._animationLoopExecuting = false;
5480
5507
  return;
5481
5508
  }
5482
5509
 
5483
- nextAnimationLoopDelayMs = 0;
5510
+ const shouldDelayAfterCursorOnlyFrame =
5511
+ cursorAnimatedThisFrame === true &&
5512
+ shouldCaptureFramePerf !== true &&
5513
+ isBoardLoading !== true &&
5514
+ isInteractiveFrame !== true &&
5515
+ shouldUpdate !== true &&
5516
+ themeAnimatedThisFrame !== true &&
5517
+ hasContinuousThemeAnimationFrame !== true &&
5518
+ shouldRenderCss3dFrame !== true &&
5519
+ shouldSyncTextOverlays !== true &&
5520
+ hasPendingAnimationWakeWork !== true &&
5521
+ hasCursorBlinkTarget === true;
5522
+ nextAnimationLoopDelayMs = shouldDelayAfterCursorOnlyFrame ? cursorBlinkWakeDelayMs : 0;
5484
5523
  this._lastAnimationLoopDelayMs = nextAnimationLoopDelayMs;
5485
5524
  this._animationLoopExecuting = false;
5486
5525
  this._scheduleAnimationLoop(loop, nextAnimationLoopDelayMs);
@@ -3551,6 +3551,7 @@
3551
3551
  { key: 'decision', iconClass: 'fa-solid fa-code-branch' },
3552
3552
  { key: 'user-check', iconClass: 'fa-solid fa-user-check' },
3553
3553
  { key: 'output', iconClass: 'fa-solid fa-file-export' },
3554
+ { key: 'network-wired', iconClass: 'fa-solid fa-network-wired' },
3554
3555
  { key: 'robot', iconClass: 'fa-solid fa-robot' }
3555
3556
  ];
3556
3557
 
@@ -6181,6 +6182,10 @@
6181
6182
 
6182
6183
  function getMemoIconKey(nodeModel) {
6183
6184
  const semanticType = getNodeSemanticType(nodeModel);
6185
+ if (semanticType === REMOTE_FLEET_SEMANTIC_TYPE) {
6186
+ return 'network-wired';
6187
+ }
6188
+
6184
6189
  if (semanticType === 'MindCanvasAgent' || semanticType === 'AgentCommand') {
6185
6190
  return 'robot';
6186
6191
  }
@@ -12230,6 +12235,86 @@
12230
12235
  return button;
12231
12236
  }
12232
12237
 
12238
+ function attachRemoteFleetTitleActions(bodyView, hostButton, stopHostButton, hostState) {
12239
+ const container = bodyView?.closest?.('.map-node-remote-fleet');
12240
+ const header = container?.querySelector?.('.map-node-memo__header') || null;
12241
+ const existingActions = header?.querySelector?.('[data-remote-fleet-title-actions="true"]') || null;
12242
+ existingActions?.remove?.();
12243
+
12244
+ const titleActions = document.createElement('div');
12245
+ titleActions.dataset.remoteFleetTitleActions = 'true';
12246
+ titleActions.style.cssText = `
12247
+ display: inline-flex;
12248
+ align-items: center;
12249
+ justify-content: flex-end;
12250
+ gap: 7px;
12251
+ min-width: 0;
12252
+ pointer-events: auto;
12253
+ `;
12254
+
12255
+ const normalizedState = String(hostState || 'inactive').trim().toLowerCase();
12256
+ const active = normalizedState === 'hosting';
12257
+ const indicator = document.createElement('span');
12258
+ indicator.dataset.remoteFleetHostIndicator = 'true';
12259
+ indicator.dataset.remoteFleetHostActive = active ? 'true' : 'false';
12260
+ indicator.title = active ? 'Host active' : 'Host inactive';
12261
+ indicator.style.cssText = `
12262
+ flex: 0 0 auto;
12263
+ width: 9px;
12264
+ height: 9px;
12265
+ border-radius: 999px;
12266
+ background: ${active ? '#2563eb' : '#94a3b8'};
12267
+ box-shadow: ${active ? '0 0 0 4px rgba(37, 99, 235, 0.14)' : '0 0 0 4px rgba(148, 163, 184, 0.14)'};
12268
+ `;
12269
+
12270
+ titleActions.appendChild(indicator);
12271
+ titleActions.appendChild(hostButton);
12272
+ if (stopHostButton) {
12273
+ titleActions.appendChild(stopHostButton);
12274
+ }
12275
+
12276
+ if (header) {
12277
+ header.style.gridTemplateColumns = '46px minmax(0, 1fr) auto';
12278
+ header.appendChild(titleActions);
12279
+ } else {
12280
+ titleActions.style.alignSelf = 'flex-end';
12281
+ bodyView?.prepend?.(titleActions);
12282
+ }
12283
+
12284
+ return titleActions;
12285
+ }
12286
+
12287
+ function createRemoteFleetEmptyScreens() {
12288
+ const shell = document.createElement('section');
12289
+ shell.dataset.remoteFleetEmptyScreens = 'true';
12290
+ shell.style.cssText = `
12291
+ flex: 1 1 auto;
12292
+ min-height: 0;
12293
+ overflow: hidden;
12294
+ display: grid;
12295
+ grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
12296
+ align-content: start;
12297
+ gap: 10px;
12298
+ padding: 2px 4px 6px 0;
12299
+ `;
12300
+
12301
+ for (let index = 0; index < 6; index += 1) {
12302
+ const screen = document.createElement('div');
12303
+ screen.dataset.remoteFleetEmptyScreen = 'true';
12304
+ screen.style.cssText = `
12305
+ aspect-ratio: 16 / 9;
12306
+ min-height: 96px;
12307
+ border-radius: 8px;
12308
+ border: 1px solid rgba(203, 213, 225, 0.72);
12309
+ background: #ffffff;
12310
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.96), 0 8px 18px rgba(15, 23, 42, 0.04);
12311
+ `;
12312
+ shell.appendChild(screen);
12313
+ }
12314
+
12315
+ return shell;
12316
+ }
12317
+
12233
12318
  function createRemoteFleetSelect(options, value, title) {
12234
12319
  const select = document.createElement('select');
12235
12320
  select.title = title || '';
@@ -12624,11 +12709,11 @@
12624
12709
 
12625
12710
  const nodeId = String(nodeModel?.id ?? nodeModel?.Id ?? '');
12626
12711
  const device = parseRemoteFleetPinnedDevice(nodeModel);
12627
- const endpoint = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHubEndpoint', '127.0.0.1:5197');
12628
12712
  const hubStatus = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHubStatus', 'offline');
12629
12713
  const refreshedAt = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetLastRefreshAtUtc', '');
12630
12714
  const lastError = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetLastError', '');
12631
12715
  const deviceId = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetPinnedDeviceId', device ? getRemoteFleetDeviceId(device) : '');
12716
+ const endpoint = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHubEndpoint', '127.0.0.1:5197');
12632
12717
 
12633
12718
  bodyView.dataset.src = `remote-device:${deviceId}:${refreshedAt}`;
12634
12719
  bodyView.classList.add('map-node-remote-fleet__body');
@@ -13173,18 +13258,11 @@
13173
13258
  devices.filter(isRemoteFleetDeviceConnected).length));
13174
13259
  const taskCapableCount = devices.filter(device => isRemoteFleetDeviceConnected(device) && isRemoteFleetDeviceTaskCapable(device)).length;
13175
13260
  const aiCapableCount = devices.filter(device => isRemoteFleetDeviceConnected(device) && isRemoteFleetDeviceAiCapable(device)).length;
13176
- const endpoint = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHubEndpoint', '127.0.0.1:5197');
13177
- const managerPackage = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetManagerPackage', '@mindexec/cli');
13178
- const managerVersion = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetManagerVersion', '');
13179
13261
  const hubStatus = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHubStatus', 'offline');
13180
13262
  const refreshedAt = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetLastRefreshAtUtc', '');
13181
13263
  const lastError = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetLastError', '');
13182
13264
  const hostTargetState = String(getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHostTargetState', 'inactive')).trim().toLowerCase();
13183
- const hostTargetNodeId = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHostTargetNodeId', '');
13184
- const hostTargetEndpoint = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHostTargetEndpoint', endpoint);
13185
- const hostTargetExpiresAt = getRemoteFleetMetadataValue(nodeModel, 'RemoteFleetHostTargetExpiresAtUtc', '');
13186
13265
  const isHostingTarget = hostTargetState === 'hosting';
13187
- const otherMonitorHosting = hostTargetState === 'other-monitor';
13188
13266
 
13189
13267
  if (!isHostingTarget) {
13190
13268
  stopRemoteFleetHostLeaseTimer(nodeId);
@@ -13206,78 +13284,8 @@
13206
13284
  background: linear-gradient(180deg, rgba(248, 250, 252, 0.96), rgba(241, 245, 249, 0.92));
13207
13285
  `;
13208
13286
 
13209
- const headerRow = document.createElement('div');
13210
- headerRow.style.cssText = `
13211
- display: flex;
13212
- align-items: flex-start;
13213
- justify-content: space-between;
13214
- gap: 10px;
13215
- flex: 0 0 auto;
13216
- min-width: 0;
13217
- `;
13218
- const headerMeta = document.createElement('div');
13219
- headerMeta.style.cssText = `
13220
- min-width: 0;
13221
- display: flex;
13222
- flex-direction: column;
13223
- gap: 4px;
13224
- `;
13225
- const managerRow = document.createElement('div');
13226
- managerRow.dataset.remoteFleetManagerVersion = 'true';
13227
- managerRow.textContent = `${managerPackage}${managerVersion ? ` ${managerVersion}` : ''} - ${endpoint}`;
13228
- managerRow.title = managerRow.textContent;
13229
- managerRow.style.cssText = `
13230
- flex: 0 0 auto;
13231
- min-width: 0;
13232
- overflow: hidden;
13233
- text-overflow: ellipsis;
13234
- white-space: nowrap;
13235
- color: #475569;
13236
- font-size: 10px;
13237
- font-weight: 850;
13238
- line-height: 1.2;
13239
- letter-spacing: 0;
13240
- `;
13241
-
13242
- const hostStatusRow = document.createElement('div');
13243
- hostStatusRow.dataset.remoteFleetHostState = 'true';
13244
- const hostStatusText = isHostingTarget
13245
- ? 'Host: Active'
13246
- : otherMonitorHosting
13247
- ? 'Host: Other monitor'
13248
- : 'Host: Inactive';
13249
- const hostLeaseText = isHostingTarget && hostTargetExpiresAt
13250
- ? ` - lease ${formatRemoteFleetTimeUntil(hostTargetExpiresAt)}`
13251
- : '';
13252
- hostStatusRow.textContent = `${hostStatusText}${hostLeaseText}`;
13253
- hostStatusRow.title = otherMonitorHosting
13254
- ? `Active host monitor: ${hostTargetNodeId || 'unknown'}`
13255
- : `Endpoint ${hostTargetEndpoint || endpoint}`;
13256
- hostStatusRow.style.cssText = `
13257
- flex: 0 0 auto;
13258
- min-width: 0;
13259
- overflow: hidden;
13260
- text-overflow: ellipsis;
13261
- white-space: nowrap;
13262
- color: ${isHostingTarget ? '#047857' : otherMonitorHosting ? '#7c2d12' : '#64748b'};
13263
- font-size: 10px;
13264
- font-weight: 900;
13265
- line-height: 1.2;
13266
- letter-spacing: 0;
13267
- `;
13268
- headerMeta.appendChild(managerRow);
13269
- headerMeta.appendChild(hostStatusRow);
13270
-
13271
- const hostActions = document.createElement('div');
13272
- hostActions.style.cssText = `
13273
- flex: 0 0 auto;
13274
- display: inline-flex;
13275
- align-items: center;
13276
- justify-content: flex-end;
13277
- gap: 6px;
13278
- `;
13279
13287
  const hostButton = createRemoteFleetButton(
13280
- isHostingTarget ? 'Hosting' : 'Set Host',
13288
+ 'Set Host',
13281
13289
  isHostingTarget
13282
13290
  ? 'Renew this monitor as the active host target for remote agents.'
13283
13291
  : 'Set this monitor as the active host target for remote agents.',
@@ -13285,9 +13293,9 @@
13285
13293
  hostButton.dataset.remoteFleetHostActive = isHostingTarget ? 'true' : 'false';
13286
13294
  hostButton.style.height = '30px';
13287
13295
  hostButton.style.minWidth = '78px';
13288
- hostButton.style.borderColor = isHostingTarget ? 'rgba(16, 185, 129, 0.38)' : 'rgba(37, 99, 235, 0.32)';
13289
- hostButton.style.background = isHostingTarget ? 'rgba(236, 253, 245, 0.96)' : '#ffffff';
13290
- hostButton.style.color = isHostingTarget ? '#047857' : '#1d4ed8';
13296
+ hostButton.style.borderColor = isHostingTarget ? 'rgba(37, 99, 235, 0.44)' : 'rgba(37, 99, 235, 0.32)';
13297
+ hostButton.style.background = isHostingTarget ? 'rgba(239, 246, 255, 0.98)' : '#ffffff';
13298
+ hostButton.style.color = '#1d4ed8';
13291
13299
  let stopHostButton = null;
13292
13300
  if (isHostingTarget) {
13293
13301
  stopHostButton = createRemoteFleetButton('Stop', 'Stop using this monitor as the host target.', 'stop-host');
@@ -13296,13 +13304,7 @@
13296
13304
  stopHostButton.style.color = '#b91c1c';
13297
13305
  stopHostButton.style.background = 'rgba(254, 242, 242, 0.94)';
13298
13306
  }
13299
- hostActions.appendChild(hostButton);
13300
- if (stopHostButton) {
13301
- hostActions.appendChild(stopHostButton);
13302
- }
13303
- headerRow.appendChild(headerMeta);
13304
- headerRow.appendChild(hostActions);
13305
- bodyView.appendChild(headerRow);
13307
+ attachRemoteFleetTitleActions(bodyView, hostButton, stopHostButton, hostTargetState);
13306
13308
 
13307
13309
  const top = document.createElement('div');
13308
13310
  top.style.cssText = `
@@ -13329,7 +13331,7 @@
13329
13331
  const searchInput = document.createElement('input');
13330
13332
  searchInput.type = 'search';
13331
13333
  searchInput.value = searchState;
13332
- searchInput.placeholder = 'Search devices';
13334
+ searchInput.placeholder = '';
13333
13335
  searchInput.dataset.remoteFleetSearch = 'true';
13334
13336
  searchInput.autocomplete = 'off';
13335
13337
  searchInput.spellcheck = false;
@@ -13401,7 +13403,7 @@
13401
13403
  filterRow.appendChild(groupSelect);
13402
13404
  filterRow.appendChild(densitySelect);
13403
13405
  filterRow.appendChild(matchCount);
13404
- bodyView.appendChild(filterRow);
13406
+ filterRow.dataset.remoteFleetAdvancedFilters = 'hidden';
13405
13407
 
13406
13408
  const taskRow = document.createElement('div');
13407
13409
  taskRow.style.cssText = `
@@ -13817,8 +13819,6 @@
13817
13819
  const createDevicePreview = (device, mode = 'tile') => {
13818
13820
  const name = getRemoteFleetDeviceName(device);
13819
13821
  const connectedDevice = isRemoteFleetDeviceConnected(device);
13820
- const thumbnailEnabled = device?.thumbnailEnabled === true || device?.ThumbnailEnabled === true;
13821
- const liveStreamEnabled = device?.liveStreamEnabled === true || device?.LiveStreamEnabled === true;
13822
13822
  const thumbnailDataUrl = String(device?.thumbnailDataUrl || device?.ThumbnailDataUrl || '');
13823
13823
  const thumbnailCapturedAt = String(device?.thumbnailCapturedAt || device?.ThumbnailCapturedAt || '');
13824
13824
  const liveFrameDataUrl = String(device?.liveFrameDataUrl || device?.LiveFrameDataUrl || '');
@@ -13837,7 +13837,7 @@
13837
13837
  aspect-ratio: 16 / 9;
13838
13838
  overflow: hidden;
13839
13839
  border-radius: ${isDetail ? '8px' : '6px'};
13840
- background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
13840
+ background: ${(hasLiveFrame || hasThumbnail) ? 'linear-gradient(135deg, #0f172a 0%, #1e293b 100%)' : '#ffffff'};
13841
13841
  border: 1px solid rgba(15, 23, 42, ${isDetail ? '0.16' : '0.10'});
13842
13842
  `;
13843
13843
 
@@ -13856,17 +13856,11 @@
13856
13856
  preview.appendChild(image);
13857
13857
  } else {
13858
13858
  const placeholder = document.createElement('div');
13859
- placeholder.textContent = thumbnailEnabled || liveStreamEnabled ? 'No screen' : 'No screen';
13859
+ placeholder.dataset.remoteFleetScreenPlaceholder = 'true';
13860
13860
  placeholder.style.cssText = `
13861
13861
  position: absolute;
13862
13862
  inset: 0;
13863
- display: flex;
13864
- align-items: center;
13865
- justify-content: center;
13866
- color: rgba(226, 232, 240, 0.80);
13867
- font-size: ${isDetail ? '12px' : '10px'};
13868
- font-weight: 900;
13869
- letter-spacing: 0;
13863
+ background: #ffffff;
13870
13864
  `;
13871
13865
  preview.appendChild(placeholder);
13872
13866
  }
@@ -14112,24 +14106,7 @@
14112
14106
  padding-right: 4px;
14113
14107
  `;
14114
14108
 
14115
- if (devices.length === 0) {
14116
- const empty = document.createElement('div');
14117
- empty.textContent = 'No devices connected yet.';
14118
- empty.style.cssText = `
14119
- grid-column: 1 / -1;
14120
- display: flex;
14121
- align-items: center;
14122
- min-height: 94px;
14123
- padding: 14px;
14124
- border-radius: 8px;
14125
- border: 1px dashed rgba(100, 116, 139, 0.36);
14126
- color: #475569;
14127
- font-size: 13px;
14128
- font-weight: 800;
14129
- background: rgba(255, 255, 255, 0.74);
14130
- `;
14131
- grid.appendChild(empty);
14132
- } else {
14109
+ if (devices.length > 0) {
14133
14110
  const groupStats = new Map();
14134
14111
  if (groupState !== 'none') {
14135
14112
  devices.forEach(device => {
@@ -14252,24 +14229,16 @@
14252
14229
  grid.appendChild(noMatch);
14253
14230
  }
14254
14231
 
14255
- monitorWorkspace.appendChild(grid);
14256
- monitorWorkspace.appendChild(createSelectedDevicePanel(selectedDevice));
14232
+ if (devices.length === 0) {
14233
+ monitorWorkspace.style.display = 'flex';
14234
+ monitorWorkspace.style.flexDirection = 'column';
14235
+ monitorWorkspace.appendChild(createRemoteFleetEmptyScreens());
14236
+ } else {
14237
+ monitorWorkspace.appendChild(grid);
14238
+ monitorWorkspace.appendChild(createSelectedDevicePanel(selectedDevice));
14239
+ }
14257
14240
  bodyView.appendChild(monitorWorkspace);
14258
14241
 
14259
- const footer = document.createElement('div');
14260
- footer.textContent = `Endpoint ${endpoint} - all devices, no paging - group ${groupState} - ${autoMonitorState ? 'auto monitor' : 'manual'} - refreshed ${formatRemoteFleetAge(refreshedAt)}`;
14261
- footer.style.cssText = `
14262
- flex: 0 0 auto;
14263
- color: #64748b;
14264
- font-size: 11px;
14265
- font-weight: 700;
14266
- line-height: 1.2;
14267
- overflow: hidden;
14268
- text-overflow: ellipsis;
14269
- white-space: nowrap;
14270
- `;
14271
- bodyView.appendChild(footer);
14272
-
14273
14242
  const setTaskFeedback = (message, tone = 'info') => {
14274
14243
  taskFeedback.textContent = message || '';
14275
14244
  taskFeedback.style.display = message ? 'block' : 'none';
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "mainAssemblyName": "MindExecution.Web",
3
3
  "resources": {
4
- "hash": "sha256-8opnBRmWTSsO9dqkwmfxCVP/1IWjG5lnkDoRFXzPaIg=",
4
+ "hash": "sha256-scsV40Knf96MECCS5TE6GBMBRc/aVh7dlYynMn/8Zjs=",
5
5
  "fingerprinting": {
6
6
  "Google.Protobuf.9h59ukbel7.dll": "Google.Protobuf.dll",
7
7
  "Markdig.d1j7v41cl1.dll": "Markdig.dll",
@@ -131,7 +131,7 @@
131
131
  "MindExecution.Plugins.Directory.y74f55e8x3.dll": "MindExecution.Plugins.Directory.dll",
132
132
  "MindExecution.Plugins.PlanMaster.jpdwbefrh1.dll": "MindExecution.Plugins.PlanMaster.dll",
133
133
  "MindExecution.Plugins.YouTube.8nz4wv2nsj.dll": "MindExecution.Plugins.YouTube.dll",
134
- "MindExecution.Shared.v6ani8nfp8.dll": "MindExecution.Shared.dll",
134
+ "MindExecution.Shared.ihh8mkcn5x.dll": "MindExecution.Shared.dll",
135
135
  "MindExecution.Web.0cs29v57jl.dll": "MindExecution.Web.dll",
136
136
  "dotnet.js": "dotnet.js",
137
137
  "dotnet.native.qc8g39g30v.js": "dotnet.native.js",
@@ -282,7 +282,7 @@
282
282
  "MindExecution.Kernel.z56elxihok.dll": "sha256-STATJelRGcW9SDGgsw6YmQi6tkQje52dy4lyT3QU4qs=",
283
283
  "MindExecution.Plugins.Concept.zczca3fsxz.dll": "sha256-A2RXdS42+UEr63Y54dc1OVeyowZIF7zUghMKuE2x11w=",
284
284
  "MindExecution.Plugins.PlanMaster.jpdwbefrh1.dll": "sha256-Z6B9yXEfheo7LEUPAbjvd5xfl1B218Gu4EVRfleOvWg=",
285
- "MindExecution.Shared.v6ani8nfp8.dll": "sha256-VzXMEqJZy84RMk0aBnLB415/HS+q83nl7RTXiNLvlPU=",
285
+ "MindExecution.Shared.ihh8mkcn5x.dll": "sha256-es97rs5TlDn5BgQqMBlWgIB0cflLmVW2shEwS3Wbajs=",
286
286
  "MindExecution.Web.0cs29v57jl.dll": "sha256-EG83w5NgZ5KzydPFrgZjTWm9BJJoX8RrOhqX81B2KPs="
287
287
  },
288
288
  "lazyAssembly": {
@@ -558,7 +558,7 @@
558
558
  }
559
559
 
560
560
  const base = '_content/MindExecution.Shared/js/';
561
- const scriptVersion = '20260612-remote-clean-monitor-v491';
561
+ const scriptVersion = '20260613-cursor-blink-remote-icon-v493';
562
562
  const scriptUrl = (script) => `${base}${script}?v=${scriptVersion}`;
563
563
  console.log(`[Script Loader] Shared JS version: ${scriptVersion}`);
564
564
  const criticalScripts = [
@@ -1,5 +1,5 @@
1
1
  self.assetsManifest = {
2
- "version": "GmythO0P",
2
+ "version": "dzmlSBJx",
3
3
  "assets": [
4
4
  {
5
5
  "hash": "sha256-+CSYMcqLNTsq3VnH11jgYyOCCdxvHzL74CBmo4sCmMU=",
@@ -78,7 +78,7 @@
78
78
  "url": "_content/MindExecution.Shared/js/marked.min.js"
79
79
  },
80
80
  {
81
- "hash": "sha256-OmYQpvRRLwtglr/vxtxNehzAsxu7ff8XxkT2YNxwLWs=",
81
+ "hash": "sha256-LtkB8IRvIm+FJDH5Kx4zNth2hyesdL4YqTPJylSkcHY=",
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-wensAgsKs3nWcJ2Ei0LTHd5i4b+baUwmP5UbK1MLG9I=",
89
+ "hash": "sha256-wZrUQUYTsiH7hkHaZPeEBdqG+r+e2UDR9vqZmyARSFg=",
90
90
  "url": "_content/MindExecution.Shared/js/mind-map-css3d-manager.js"
91
91
  },
92
92
  {
@@ -442,8 +442,8 @@
442
442
  "url": "_framework/MindExecution.Plugins.YouTube.8nz4wv2nsj.dll"
443
443
  },
444
444
  {
445
- "hash": "sha256-VzXMEqJZy84RMk0aBnLB415/HS+q83nl7RTXiNLvlPU=",
446
- "url": "_framework/MindExecution.Shared.v6ani8nfp8.dll"
445
+ "hash": "sha256-es97rs5TlDn5BgQqMBlWgIB0cflLmVW2shEwS3Wbajs=",
446
+ "url": "_framework/MindExecution.Shared.ihh8mkcn5x.dll"
447
447
  },
448
448
  {
449
449
  "hash": "sha256-EG83w5NgZ5KzydPFrgZjTWm9BJJoX8RrOhqX81B2KPs=",
@@ -770,7 +770,7 @@
770
770
  "url": "_framework/Websocket.Client.vapounvmnl.dll"
771
771
  },
772
772
  {
773
- "hash": "sha256-SnKuukkikWBroUqaw7qwE4nOor1NpE81zg2DHpQCNtw=",
773
+ "hash": "sha256-F7qorSpP5AuTT8FXK8Ya++UIan89IZHyuh+KPEHYUoA=",
774
774
  "url": "_framework/blazor.boot.json"
775
775
  },
776
776
  {
@@ -834,7 +834,7 @@
834
834
  "url": "image-manifest.json"
835
835
  },
836
836
  {
837
- "hash": "sha256-OrDc5s2gYCFEgTQDxl3FK0xyRLULLSusjX3jgP+azHQ=",
837
+ "hash": "sha256-6JYaZ2X3Gg9XoAZomsa4QjcmaQDb7YpKcGVGvxLSXTg=",
838
838
  "url": "index.html"
839
839
  },
840
840
  {
@@ -1,4 +1,4 @@
1
- /* Manifest version: GmythO0P */
1
+ /* Manifest version: dzmlSBJx */
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