mrmd-editor 0.6.0 → 0.7.1
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 +1 -1
- package/src/cell-controls/widgets.js +30 -0
- package/src/cells.js +9 -9
- package/src/ctrl-k-modal.js +190 -14
- package/src/document-languages.js +105 -0
- package/src/execution.js +73 -25
- package/src/frontmatter-updater.js +224 -0
- package/src/index.js +173 -86
- package/src/markdown/renderer.js +52 -3
- package/src/markdown/styles.js +126 -0
- package/src/monitor-coordination.js +1 -3
- package/src/mrp-client.js +36 -169
- package/src/mrp-types.js +1 -37
- package/src/output-widget.js +54 -0
- package/src/runtime-codelens/index.js +3 -3
- package/src/shell/ai-menu.js +70 -0
- package/src/shell/components/menu.js +39 -1
- package/src/shell/components/status-bar.js +213 -11
- package/src/shell/dialogs/file-picker.js +378 -6
- package/src/shell/layouts/studio.js +31 -9
- package/src/shell/orchestrator-client.js +105 -18
- package/src/shell/state.js +63 -42
- package/src/shell/styles.js +328 -0
- package/src/term-pty-client.js +62 -7
- package/src/widgets/theme-utils.js +12 -12
- package/src/widgets/theme.js +520 -0
package/src/output-widget.js
CHANGED
|
@@ -2423,6 +2423,60 @@ ${ansiStyles}
|
|
|
2423
2423
|
.cm-stdin-widget[data-password="true"] .cm-stdin-content {
|
|
2424
2424
|
letter-spacing: 0.2em;
|
|
2425
2425
|
}
|
|
2426
|
+
|
|
2427
|
+
/* ==========================================================================
|
|
2428
|
+
MOBILE RESPONSIVE
|
|
2429
|
+
|
|
2430
|
+
Output blocks need to be readable on narrow screens.
|
|
2431
|
+
Horizontal scroll for wide output, larger text for readability.
|
|
2432
|
+
========================================================================== */
|
|
2433
|
+
|
|
2434
|
+
@media (max-width: 768px) {
|
|
2435
|
+
.cm-output-widget {
|
|
2436
|
+
font-size: max(var(--output-font-size, 0.7em), 12px);
|
|
2437
|
+
left: 0; /* No inset on mobile — use full width */
|
|
2438
|
+
right: 0;
|
|
2439
|
+
padding: 8px 12px;
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
/* Output content lines: ensure they don't overflow */
|
|
2443
|
+
.cm-output-content-line {
|
|
2444
|
+
font-size: max(var(--output-font-size, 0.8em), 12px);
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
/* Rich output (HTML renders, plots): full width */
|
|
2448
|
+
.cm-output-rich-widget {
|
|
2449
|
+
max-width: 100%;
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
.cm-output-rich-widget iframe {
|
|
2453
|
+
max-width: 100%;
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
/* Stdin widget: bigger input on mobile */
|
|
2457
|
+
.cm-stdin-widget .cm-stdin-input {
|
|
2458
|
+
font-size: 16px; /* Prevents iOS zoom */
|
|
2459
|
+
padding: 10px;
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
.cm-stdin-widget .cm-stdin-prompt {
|
|
2463
|
+
font-size: 14px;
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
@media (pointer: coarse) {
|
|
2468
|
+
/* Output container: larger tap target for focus/interaction */
|
|
2469
|
+
.cm-output-widget {
|
|
2470
|
+
padding: 10px 14px;
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
/* Collapsed output: easy to tap to expand */
|
|
2474
|
+
.cm-output-collapsed {
|
|
2475
|
+
min-height: 44px;
|
|
2476
|
+
display: flex;
|
|
2477
|
+
align-items: center;
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2426
2480
|
`;
|
|
2427
2481
|
|
|
2428
2482
|
// #endregion STYLES
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
* const extensions = createRuntimeCodeLensExtensions({
|
|
12
12
|
* projectName: 'my-project',
|
|
13
13
|
* getSessionStatus: (name) => shellState.get(`runtimes.sessions.${name}`),
|
|
14
|
-
* onStart: async (runtime) => { await electronAPI.
|
|
15
|
-
* onStop: async (name) => { await electronAPI.
|
|
16
|
-
* onRestart: async (name) => { await electronAPI.
|
|
14
|
+
* onStart: async (runtime) => { await electronAPI.runtime.start(runtime); },
|
|
15
|
+
* onStop: async (name) => { await electronAPI.runtime.stop(name); },
|
|
16
|
+
* onRestart: async (name) => { await electronAPI.runtime.restart(name); },
|
|
17
17
|
* onRestartAll: async () => { ... },
|
|
18
18
|
* });
|
|
19
19
|
* ```
|
package/src/shell/ai-menu.js
CHANGED
|
@@ -414,6 +414,76 @@ export const AI_MENU_STYLES = `
|
|
|
414
414
|
color: #808080;
|
|
415
415
|
font-style: italic;
|
|
416
416
|
}
|
|
417
|
+
|
|
418
|
+
/* Mobile: AI menu becomes a bottom sheet */
|
|
419
|
+
@media (max-width: 768px) {
|
|
420
|
+
.ai-menu {
|
|
421
|
+
position: fixed !important;
|
|
422
|
+
left: 0 !important;
|
|
423
|
+
right: 0 !important;
|
|
424
|
+
bottom: 0 !important;
|
|
425
|
+
top: auto !important;
|
|
426
|
+
min-width: 100%;
|
|
427
|
+
max-width: 100%;
|
|
428
|
+
border-radius: 16px 16px 0 0;
|
|
429
|
+
border: none;
|
|
430
|
+
border-top: 1px solid #3c3c3c;
|
|
431
|
+
box-shadow: 0 -4px 32px rgba(0, 0, 0, 0.4);
|
|
432
|
+
animation: ai-menu-slide-up 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
@keyframes ai-menu-slide-up {
|
|
436
|
+
from { transform: translateY(100%); }
|
|
437
|
+
to { transform: translateY(0); }
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.ai-menu-header {
|
|
441
|
+
padding: 16px 20px;
|
|
442
|
+
position: relative;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.ai-menu-header::before {
|
|
446
|
+
content: '';
|
|
447
|
+
position: absolute;
|
|
448
|
+
top: 8px;
|
|
449
|
+
left: 50%;
|
|
450
|
+
transform: translateX(-50%);
|
|
451
|
+
width: 36px;
|
|
452
|
+
height: 4px;
|
|
453
|
+
background: #666;
|
|
454
|
+
border-radius: 2px;
|
|
455
|
+
opacity: 0.4;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.ai-menu-title {
|
|
459
|
+
font-size: 16px;
|
|
460
|
+
padding-top: 4px;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.ai-menu-juice {
|
|
464
|
+
font-size: 14px;
|
|
465
|
+
padding: 6px 12px;
|
|
466
|
+
border-radius: 8px;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.ai-menu-commands {
|
|
470
|
+
max-height: 50vh;
|
|
471
|
+
padding: 8px 0;
|
|
472
|
+
padding-bottom: calc(8px + env(safe-area-inset-bottom, 0px));
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.ai-menu-item {
|
|
476
|
+
padding: 14px 20px;
|
|
477
|
+
min-height: 48px;
|
|
478
|
+
font-size: 16px;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.ai-menu-item-icon {
|
|
482
|
+
width: 28px;
|
|
483
|
+
font-size: 18px;
|
|
484
|
+
margin-right: 12px;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
417
487
|
`;
|
|
418
488
|
|
|
419
489
|
/**
|
|
@@ -55,8 +55,21 @@ export function createMenu(options) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
//
|
|
58
|
+
// Detect mobile/narrow viewport
|
|
59
|
+
const isMobile = () => window.matchMedia('(max-width: 768px)').matches;
|
|
60
|
+
|
|
61
|
+
// Position menu relative to anchor (desktop) or as bottom sheet (mobile)
|
|
59
62
|
function updatePosition() {
|
|
63
|
+
// On mobile, CSS handles the positioning (bottom sheet via @media queries).
|
|
64
|
+
// We just need to not set inline position styles that would conflict.
|
|
65
|
+
if (isMobile()) {
|
|
66
|
+
menu.style.top = '';
|
|
67
|
+
menu.style.left = '';
|
|
68
|
+
menu.style.right = '';
|
|
69
|
+
menu.style.bottom = '';
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
60
73
|
const anchorRect = anchor.getBoundingClientRect();
|
|
61
74
|
const menuRect = menu.getBoundingClientRect();
|
|
62
75
|
|
|
@@ -96,10 +109,18 @@ export function createMenu(options) {
|
|
|
96
109
|
menu.style.left = `${left}px`;
|
|
97
110
|
}
|
|
98
111
|
|
|
112
|
+
// Mobile scrim backdrop (for bottom sheet pattern)
|
|
113
|
+
let scrim = null;
|
|
114
|
+
|
|
99
115
|
// Close menu
|
|
100
116
|
function close() {
|
|
101
117
|
menu.remove();
|
|
118
|
+
if (scrim) {
|
|
119
|
+
scrim.remove();
|
|
120
|
+
scrim = null;
|
|
121
|
+
}
|
|
102
122
|
document.removeEventListener('mousedown', handleOutsideClick);
|
|
123
|
+
document.removeEventListener('touchstart', handleOutsideClick);
|
|
103
124
|
document.removeEventListener('keydown', handleKeydown);
|
|
104
125
|
onClose?.();
|
|
105
126
|
}
|
|
@@ -120,11 +141,28 @@ export function createMenu(options) {
|
|
|
120
141
|
|
|
121
142
|
// Initialize
|
|
122
143
|
render(items);
|
|
144
|
+
|
|
145
|
+
// On mobile, add a scrim backdrop behind the bottom sheet
|
|
146
|
+
if (isMobile()) {
|
|
147
|
+
scrim = document.createElement('div');
|
|
148
|
+
scrim.style.cssText = `
|
|
149
|
+
position: fixed;
|
|
150
|
+
inset: 0;
|
|
151
|
+
background: rgba(0, 0, 0, 0.4);
|
|
152
|
+
z-index: 999;
|
|
153
|
+
animation: mrmd-fade-in 0.15s ease;
|
|
154
|
+
`;
|
|
155
|
+
scrim.addEventListener('click', close);
|
|
156
|
+
scrim.addEventListener('touchstart', close);
|
|
157
|
+
document.body.appendChild(scrim);
|
|
158
|
+
}
|
|
159
|
+
|
|
123
160
|
document.body.appendChild(menu);
|
|
124
161
|
updatePosition();
|
|
125
162
|
|
|
126
163
|
// Add event listeners
|
|
127
164
|
document.addEventListener('mousedown', handleOutsideClick);
|
|
165
|
+
document.addEventListener('touchstart', handleOutsideClick);
|
|
128
166
|
document.addEventListener('keydown', handleKeydown);
|
|
129
167
|
|
|
130
168
|
return {
|
|
@@ -861,6 +861,80 @@ function createRuntimeSegment({ shellState, orchestratorClient, handlers, onClea
|
|
|
861
861
|
});
|
|
862
862
|
}
|
|
863
863
|
|
|
864
|
+
// Runtime machine selection
|
|
865
|
+
if (machineState.supported) {
|
|
866
|
+
items.push({ type: 'divider' });
|
|
867
|
+
items.push({ type: 'header', label: 'Runtime Machine' });
|
|
868
|
+
|
|
869
|
+
items.push({
|
|
870
|
+
icon: activeMachineId ? '○' : '✓',
|
|
871
|
+
label: 'Auto-select',
|
|
872
|
+
active: !activeMachineId,
|
|
873
|
+
onClick: async () => {
|
|
874
|
+
try {
|
|
875
|
+
await orchestratorClient.setActiveMachine?.(null);
|
|
876
|
+
await refreshMachineState({ doRender: true });
|
|
877
|
+
} catch (err) {
|
|
878
|
+
console.error('Failed to set auto machine:', err);
|
|
879
|
+
}
|
|
880
|
+
},
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
for (const m of machineList) {
|
|
884
|
+
const online = m.status === 'online' || m.connected === true;
|
|
885
|
+
const isActiveMachine = activeMachineId === m.machineId;
|
|
886
|
+
items.push({
|
|
887
|
+
icon: isActiveMachine ? '✓' : (online ? '●' : '○'),
|
|
888
|
+
label: `${m.machineName || m.machineId}${online ? '' : ' (offline)'}`,
|
|
889
|
+
active: isActiveMachine,
|
|
890
|
+
onClick: async () => {
|
|
891
|
+
try {
|
|
892
|
+
await orchestratorClient.setActiveMachine?.(m.machineId);
|
|
893
|
+
await refreshMachineState({ doRender: true });
|
|
894
|
+
} catch (err) {
|
|
895
|
+
console.error('Failed to switch machine:', err);
|
|
896
|
+
}
|
|
897
|
+
},
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
if (projectRoot) {
|
|
902
|
+
const pref = getProjectMachinePreference(projectRoot);
|
|
903
|
+
items.push({ type: 'divider' });
|
|
904
|
+
items.push({ type: 'header', label: 'Project Machine Preference' });
|
|
905
|
+
|
|
906
|
+
items.push({
|
|
907
|
+
icon: pref === undefined ? '✓' : '○',
|
|
908
|
+
label: 'No preference (manual/global)',
|
|
909
|
+
active: pref === undefined,
|
|
910
|
+
onClick: () => {
|
|
911
|
+
setProjectMachinePreference(projectRoot, undefined);
|
|
912
|
+
},
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
items.push({
|
|
916
|
+
icon: pref === null ? '✓' : '○',
|
|
917
|
+
label: 'Prefer auto-select',
|
|
918
|
+
active: pref === null,
|
|
919
|
+
onClick: () => {
|
|
920
|
+
setProjectMachinePreference(projectRoot, null);
|
|
921
|
+
},
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
for (const m of machineList) {
|
|
925
|
+
const isPref = pref === m.machineId;
|
|
926
|
+
items.push({
|
|
927
|
+
icon: isPref ? '✓' : '📌',
|
|
928
|
+
label: `Prefer ${m.machineName || m.machineId}`,
|
|
929
|
+
active: isPref,
|
|
930
|
+
onClick: () => {
|
|
931
|
+
setProjectMachinePreference(projectRoot, m.machineId);
|
|
932
|
+
},
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
864
938
|
// Refresh action
|
|
865
939
|
items.push({ type: 'divider' });
|
|
866
940
|
items.push({
|
|
@@ -870,6 +944,7 @@ function createRuntimeSegment({ shellState, orchestratorClient, handlers, onClea
|
|
|
870
944
|
cachedRuntimes = null;
|
|
871
945
|
cachedVenvs = null;
|
|
872
946
|
await fetchRuntimes();
|
|
947
|
+
await refreshMachineState({ doRender: false });
|
|
873
948
|
render();
|
|
874
949
|
},
|
|
875
950
|
});
|
|
@@ -885,13 +960,30 @@ function createRuntimeSegment({ shellState, orchestratorClient, handlers, onClea
|
|
|
885
960
|
segment.addEventListener('click', openMenu);
|
|
886
961
|
|
|
887
962
|
// Initial fetch
|
|
888
|
-
|
|
963
|
+
Promise.all([
|
|
964
|
+
fetchRuntimes(),
|
|
965
|
+
refreshMachineState({ doRender: false }),
|
|
966
|
+
]).then(async () => {
|
|
967
|
+
render();
|
|
968
|
+
await applyProjectPreference();
|
|
969
|
+
});
|
|
889
970
|
|
|
890
971
|
// Subscribe to state changes
|
|
891
972
|
const unsubscribe1 = shellState.onPath('runtimes', render);
|
|
892
973
|
const unsubscribe2 = shellState.onPath('file', render);
|
|
974
|
+
const unsubscribe3 = shellState.onPath('projectRoot', () => {
|
|
975
|
+
applyProjectPreference();
|
|
976
|
+
render();
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
const machinePoll = setInterval(() => {
|
|
980
|
+
refreshMachineState({ doRender: true });
|
|
981
|
+
}, 15000);
|
|
982
|
+
|
|
893
983
|
onCleanup(unsubscribe1);
|
|
894
984
|
onCleanup(unsubscribe2);
|
|
985
|
+
onCleanup(unsubscribe3);
|
|
986
|
+
onCleanup(() => clearInterval(machinePoll));
|
|
895
987
|
onCleanup(() => currentMenu?.close());
|
|
896
988
|
|
|
897
989
|
render();
|
|
@@ -1402,7 +1494,7 @@ function createRuntimesSegment({ shellState, orchestratorClient, handlers, onCle
|
|
|
1402
1494
|
let runtimeCount = 0;
|
|
1403
1495
|
if (python?.running || python?.status === 'ready') runtimeCount++;
|
|
1404
1496
|
|
|
1405
|
-
const sessions = shellState.
|
|
1497
|
+
const sessions = shellState.getRuntimes();
|
|
1406
1498
|
const dedicatedCount = sessions.filter(s => s.info?.dedicated).length;
|
|
1407
1499
|
runtimeCount += dedicatedCount;
|
|
1408
1500
|
|
|
@@ -1650,7 +1742,7 @@ function createRuntimesSegment({ shellState, orchestratorClient, handlers, onCle
|
|
|
1650
1742
|
items.push({ type: 'divider' });
|
|
1651
1743
|
items.push({
|
|
1652
1744
|
type: 'header',
|
|
1653
|
-
label: `Active
|
|
1745
|
+
label: `Active Runtime Attachments (${runtimes.sessions.length})`,
|
|
1654
1746
|
});
|
|
1655
1747
|
|
|
1656
1748
|
for (const session of runtimes.sessions) {
|
|
@@ -1671,16 +1763,16 @@ function createRuntimesSegment({ shellState, orchestratorClient, handlers, onCle
|
|
|
1671
1763
|
});
|
|
1672
1764
|
items.push({
|
|
1673
1765
|
icon: '✖',
|
|
1674
|
-
label: `
|
|
1766
|
+
label: `Detach runtime from "${session.doc}"`,
|
|
1675
1767
|
description: 'Stops monitor',
|
|
1676
1768
|
onClick: async () => {
|
|
1677
1769
|
try {
|
|
1678
|
-
await orchestratorClient.
|
|
1770
|
+
await orchestratorClient.destroyRuntimeAttachment(session.doc);
|
|
1679
1771
|
cachedRuntimes = null;
|
|
1680
1772
|
lastFetchTime = 0;
|
|
1681
1773
|
render();
|
|
1682
1774
|
} catch (err) {
|
|
1683
|
-
console.error('Failed to
|
|
1775
|
+
console.error('Failed to detach runtime attachment:', err);
|
|
1684
1776
|
}
|
|
1685
1777
|
},
|
|
1686
1778
|
});
|
|
@@ -1746,6 +1838,101 @@ function createSimpleSegment({ shellState, orchestratorClient, handlers, onClean
|
|
|
1746
1838
|
|
|
1747
1839
|
let currentMenu = null;
|
|
1748
1840
|
let runtimeState = 'disconnected'; // 'connected', 'disconnected', 'error'
|
|
1841
|
+
let machineState = {
|
|
1842
|
+
supported: false,
|
|
1843
|
+
activeMachineId: null,
|
|
1844
|
+
activeMachineName: null,
|
|
1845
|
+
machines: [],
|
|
1846
|
+
};
|
|
1847
|
+
|
|
1848
|
+
const PROJECT_MACHINE_PREF_KEY = 'mrmd:project-machine-pref:v1';
|
|
1849
|
+
let projectMachinePrefs = loadProjectMachinePrefs();
|
|
1850
|
+
let lastAppliedProjectPrefKey = null;
|
|
1851
|
+
|
|
1852
|
+
function loadProjectMachinePrefs() {
|
|
1853
|
+
try {
|
|
1854
|
+
const raw = window.localStorage?.getItem(PROJECT_MACHINE_PREF_KEY);
|
|
1855
|
+
const parsed = raw ? JSON.parse(raw) : {};
|
|
1856
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
1857
|
+
} catch {
|
|
1858
|
+
return {};
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
function saveProjectMachinePrefs() {
|
|
1863
|
+
try {
|
|
1864
|
+
window.localStorage?.setItem(PROJECT_MACHINE_PREF_KEY, JSON.stringify(projectMachinePrefs));
|
|
1865
|
+
} catch {
|
|
1866
|
+
// ignore
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
function getProjectMachinePreference(projectRoot) {
|
|
1871
|
+
if (!projectRoot || !(projectRoot in projectMachinePrefs)) return undefined;
|
|
1872
|
+
const value = projectMachinePrefs[projectRoot];
|
|
1873
|
+
return value === '__auto__' ? null : value;
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
function setProjectMachinePreference(projectRoot, machineIdOrNullOrUndefined) {
|
|
1877
|
+
if (!projectRoot) return;
|
|
1878
|
+
if (machineIdOrNullOrUndefined === undefined) {
|
|
1879
|
+
delete projectMachinePrefs[projectRoot];
|
|
1880
|
+
} else if (machineIdOrNullOrUndefined === null) {
|
|
1881
|
+
projectMachinePrefs[projectRoot] = '__auto__';
|
|
1882
|
+
} else {
|
|
1883
|
+
projectMachinePrefs[projectRoot] = machineIdOrNullOrUndefined;
|
|
1884
|
+
}
|
|
1885
|
+
saveProjectMachinePrefs();
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
async function refreshMachineState({ doRender = true } = {}) {
|
|
1889
|
+
try {
|
|
1890
|
+
const [machinesRes, activeRes] = await Promise.all([
|
|
1891
|
+
orchestratorClient.getMachines?.(),
|
|
1892
|
+
orchestratorClient.getActiveMachine?.(),
|
|
1893
|
+
]);
|
|
1894
|
+
|
|
1895
|
+
const machines = machinesRes?.machines || [];
|
|
1896
|
+
const activeMachineId = activeRes?.activeMachineId || null;
|
|
1897
|
+
const activeMachine = machines.find(m => m.machineId === activeMachineId) || null;
|
|
1898
|
+
|
|
1899
|
+
machineState = {
|
|
1900
|
+
supported: true,
|
|
1901
|
+
activeMachineId,
|
|
1902
|
+
activeMachineName: activeMachine?.machineName || activeMachine?.machineId || null,
|
|
1903
|
+
machines,
|
|
1904
|
+
};
|
|
1905
|
+
} catch {
|
|
1906
|
+
machineState = {
|
|
1907
|
+
supported: false,
|
|
1908
|
+
activeMachineId: null,
|
|
1909
|
+
activeMachineName: null,
|
|
1910
|
+
machines: [],
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
if (doRender) render();
|
|
1915
|
+
return machineState;
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
async function applyProjectPreference() {
|
|
1919
|
+
const projectRoot = shellState.get('projectRoot');
|
|
1920
|
+
if (!projectRoot || !machineState.supported) return;
|
|
1921
|
+
|
|
1922
|
+
const pref = getProjectMachinePreference(projectRoot);
|
|
1923
|
+
if (pref === undefined) return; // no preference set
|
|
1924
|
+
|
|
1925
|
+
const key = `${projectRoot}|${pref === null ? '__auto__' : pref}`;
|
|
1926
|
+
if (key === lastAppliedProjectPrefKey) return;
|
|
1927
|
+
lastAppliedProjectPrefKey = key;
|
|
1928
|
+
|
|
1929
|
+
try {
|
|
1930
|
+
await orchestratorClient.setActiveMachine?.(pref);
|
|
1931
|
+
await refreshMachineState({ doRender: true });
|
|
1932
|
+
} catch {
|
|
1933
|
+
// ignore
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1749
1936
|
|
|
1750
1937
|
function render() {
|
|
1751
1938
|
const file = shellState.get('file');
|
|
@@ -1777,11 +1964,18 @@ function createSimpleSegment({ shellState, orchestratorClient, handlers, onClean
|
|
|
1777
1964
|
error: 'No venv selected - click to pick one',
|
|
1778
1965
|
}[runtimeState];
|
|
1779
1966
|
|
|
1967
|
+
const machinePill = machineState.supported
|
|
1968
|
+
? `<span class="mrmd-statusbar__machine-pill" title="Active runtime machine — click to change">⚡ ${machineState.activeMachineName || 'Auto'}</span>`
|
|
1969
|
+
: '';
|
|
1970
|
+
|
|
1780
1971
|
segment.innerHTML = `
|
|
1781
1972
|
<span class="mrmd-statusbar__filename" style="font-weight: 500;">${filename}</span>
|
|
1782
|
-
<span
|
|
1783
|
-
|
|
1784
|
-
|
|
1973
|
+
<span style="display:inline-flex; align-items:center; gap:8px;">
|
|
1974
|
+
${machinePill}
|
|
1975
|
+
<span class="mrmd-statusbar__runtime-dot"
|
|
1976
|
+
style="width: 10px; height: 10px; border-radius: 50%; background: ${dotColor}; cursor: pointer; ${runtimeState === 'error' ? 'animation: blink 1s infinite;' : ''}"
|
|
1977
|
+
title="${dotTitle}"></span>
|
|
1978
|
+
</span>
|
|
1785
1979
|
`;
|
|
1786
1980
|
|
|
1787
1981
|
// Add blink animation if needed
|
|
@@ -1794,8 +1988,10 @@ function createSimpleSegment({ shellState, orchestratorClient, handlers, onClean
|
|
|
1794
1988
|
}
|
|
1795
1989
|
|
|
1796
1990
|
async function openMenu(e) {
|
|
1797
|
-
// Only open menu when clicking the dot
|
|
1798
|
-
|
|
1991
|
+
// Only open menu when clicking the dot or machine pill
|
|
1992
|
+
const isRuntimeDot = e.target.classList.contains('mrmd-statusbar__runtime-dot');
|
|
1993
|
+
const isMachinePill = e.target.classList.contains('mrmd-statusbar__machine-pill');
|
|
1994
|
+
if (!isRuntimeDot && !isMachinePill) {
|
|
1799
1995
|
return;
|
|
1800
1996
|
}
|
|
1801
1997
|
|
|
@@ -1832,6 +2028,12 @@ function createSimpleSegment({ shellState, orchestratorClient, handlers, onClean
|
|
|
1832
2028
|
console.error('Failed to fetch runtimes/venvs:', err);
|
|
1833
2029
|
}
|
|
1834
2030
|
|
|
2031
|
+
// Fetch machine state (if available in cloud mode)
|
|
2032
|
+
await refreshMachineState({ doRender: false });
|
|
2033
|
+
const machineList = machineState.machines || [];
|
|
2034
|
+
const activeMachineId = machineState.activeMachineId || null;
|
|
2035
|
+
const projectRoot = shellState.get('projectRoot');
|
|
2036
|
+
|
|
1835
2037
|
// Section 1: Running Runtimes
|
|
1836
2038
|
items.push({
|
|
1837
2039
|
type: 'header',
|