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/shell/state.js
CHANGED
|
@@ -69,12 +69,12 @@ function getInitialState() {
|
|
|
69
69
|
runtimes: {
|
|
70
70
|
// Legacy: single Python runtime info (for backward compat)
|
|
71
71
|
python: null,
|
|
72
|
-
//
|
|
72
|
+
// Runtime registry
|
|
73
73
|
sessions: {
|
|
74
74
|
// 'shared': { id, url, status, venv, cwd, ... }
|
|
75
75
|
// 'python-8001': { id, url, status, venv, cwd, dedicated: true, port: 8001 }
|
|
76
76
|
},
|
|
77
|
-
// Document →
|
|
77
|
+
// Document → runtime attachment
|
|
78
78
|
attachments: {
|
|
79
79
|
// 'my-notebook': 'shared'
|
|
80
80
|
// 'data-analysis': 'python-8001'
|
|
@@ -464,55 +464,55 @@ export class ShellStateManager {
|
|
|
464
464
|
}
|
|
465
465
|
|
|
466
466
|
// ===========================================================================
|
|
467
|
-
// Runtime
|
|
467
|
+
// Runtime Attachment Management
|
|
468
468
|
// ===========================================================================
|
|
469
469
|
|
|
470
470
|
/**
|
|
471
|
-
* Get the
|
|
471
|
+
* Get the runtime attached to a document
|
|
472
472
|
* @param {string} docName - Document name
|
|
473
|
-
* @returns {string}
|
|
473
|
+
* @returns {string} Runtime ID (defaults to 'shared')
|
|
474
474
|
*/
|
|
475
|
-
|
|
475
|
+
getDocumentRuntime(docName) {
|
|
476
476
|
return this.get(`runtimes.attachments.${docName}`) || 'shared';
|
|
477
477
|
}
|
|
478
478
|
|
|
479
479
|
/**
|
|
480
|
-
* Get
|
|
481
|
-
* @param {string}
|
|
480
|
+
* Get runtime info
|
|
481
|
+
* @param {string} runtimeId - Runtime ID
|
|
482
482
|
* @returns {Object|null}
|
|
483
483
|
*/
|
|
484
|
-
|
|
485
|
-
return this.get(`runtimes.sessions.${
|
|
484
|
+
getRuntime(runtimeId) {
|
|
485
|
+
return this.get(`runtimes.sessions.${runtimeId}`) || null;
|
|
486
486
|
}
|
|
487
487
|
|
|
488
488
|
/**
|
|
489
|
-
* Get all available
|
|
489
|
+
* Get all available runtimes
|
|
490
490
|
* @returns {Array<{id: string, info: Object}>}
|
|
491
491
|
*/
|
|
492
|
-
|
|
493
|
-
const
|
|
494
|
-
return Object.entries(
|
|
492
|
+
getRuntimes() {
|
|
493
|
+
const runtimes = this.get('runtimes.sessions') || {};
|
|
494
|
+
return Object.entries(runtimes).map(([id, info]) => ({ id, info }));
|
|
495
495
|
}
|
|
496
496
|
|
|
497
497
|
/**
|
|
498
|
-
* Attach a document to a
|
|
498
|
+
* Attach a document to a runtime
|
|
499
499
|
* @param {string} docName - Document name
|
|
500
|
-
* @param {string}
|
|
500
|
+
* @param {string} runtimeId - Runtime ID to attach to
|
|
501
501
|
*/
|
|
502
|
-
attachDocument(docName,
|
|
503
|
-
this._set(`runtimes.attachments.${docName}`,
|
|
502
|
+
attachDocument(docName, runtimeId) {
|
|
503
|
+
this._set(`runtimes.attachments.${docName}`, runtimeId);
|
|
504
504
|
}
|
|
505
505
|
|
|
506
506
|
/**
|
|
507
|
-
* Register
|
|
508
|
-
* @param {string}
|
|
509
|
-
* @param {Object} info -
|
|
507
|
+
* Register runtime metadata
|
|
508
|
+
* @param {string} runtimeId - Runtime ID
|
|
509
|
+
* @param {Object} info - Runtime info (url, status, venv, cwd, etc.)
|
|
510
510
|
*/
|
|
511
|
-
|
|
512
|
-
this._set(`runtimes.sessions.${
|
|
511
|
+
registerRuntime(runtimeId, info) {
|
|
512
|
+
this._set(`runtimes.sessions.${runtimeId}`, info);
|
|
513
513
|
|
|
514
|
-
// Also update legacy python state if this is the shared
|
|
515
|
-
if (
|
|
514
|
+
// Also update legacy python state if this is the shared runtime
|
|
515
|
+
if (runtimeId === 'shared' && info.language === 'python') {
|
|
516
516
|
this._set('runtimes.python', {
|
|
517
517
|
language: 'python',
|
|
518
518
|
version: info.version,
|
|
@@ -527,20 +527,20 @@ export class ShellStateManager {
|
|
|
527
527
|
}
|
|
528
528
|
|
|
529
529
|
/**
|
|
530
|
-
* Create
|
|
531
|
-
* @param {string} docName - Document to attach
|
|
530
|
+
* Create and attach a runtime
|
|
531
|
+
* @param {string} docName - Document to attach runtime to
|
|
532
532
|
* @param {'shared'|'dedicated'} mode - Runtime mode
|
|
533
533
|
* @param {string} [venv] - Path to virtual environment (for dedicated runtimes)
|
|
534
|
-
* @returns {Promise<Object>}
|
|
534
|
+
* @returns {Promise<Object>} Runtime info
|
|
535
535
|
*/
|
|
536
|
-
async
|
|
536
|
+
async createRuntime(docName, mode = 'dedicated', venv = null) {
|
|
537
537
|
try {
|
|
538
|
-
const result = await this._client.
|
|
538
|
+
const result = await this._client.createRuntimeAttachment(docName, mode, venv);
|
|
539
539
|
|
|
540
|
-
const
|
|
540
|
+
const runtimeId = result.id || (mode === 'dedicated' ? `python-${result.runtimes?.python?.port || Date.now()}` : 'shared');
|
|
541
541
|
|
|
542
|
-
const
|
|
543
|
-
id:
|
|
542
|
+
const runtimeInfo = {
|
|
543
|
+
id: runtimeId,
|
|
544
544
|
url: result.runtimes?.python?.url || result.sync,
|
|
545
545
|
status: 'ready',
|
|
546
546
|
dedicated: mode === 'dedicated',
|
|
@@ -550,15 +550,15 @@ export class ShellStateManager {
|
|
|
550
550
|
docName,
|
|
551
551
|
};
|
|
552
552
|
|
|
553
|
-
// Register
|
|
554
|
-
this.
|
|
553
|
+
// Register runtime
|
|
554
|
+
this.registerRuntime(runtimeId, runtimeInfo);
|
|
555
555
|
|
|
556
|
-
// Attach
|
|
557
|
-
this.attachDocument(docName,
|
|
556
|
+
// Attach document to runtime
|
|
557
|
+
this.attachDocument(docName, runtimeId);
|
|
558
558
|
|
|
559
|
-
return
|
|
559
|
+
return runtimeInfo;
|
|
560
560
|
} catch (error) {
|
|
561
|
-
console.error('Failed to create
|
|
561
|
+
console.error('Failed to create runtime:', error);
|
|
562
562
|
throw error;
|
|
563
563
|
}
|
|
564
564
|
}
|
|
@@ -569,9 +569,30 @@ export class ShellStateManager {
|
|
|
569
569
|
* @returns {string|null} Runtime URL
|
|
570
570
|
*/
|
|
571
571
|
getRuntimeUrl(docName) {
|
|
572
|
-
const
|
|
573
|
-
const
|
|
574
|
-
return
|
|
572
|
+
const runtimeId = this.getDocumentRuntime(docName);
|
|
573
|
+
const runtime = this.getRuntime(runtimeId);
|
|
574
|
+
return runtime?.url || null;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Legacy aliases
|
|
578
|
+
getDocumentSession(docName) {
|
|
579
|
+
return this.getDocumentRuntime(docName);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
getSession(sessionId) {
|
|
583
|
+
return this.getRuntime(sessionId);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
getSessions() {
|
|
587
|
+
return this.getRuntimes();
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
registerSession(sessionId, info) {
|
|
591
|
+
this.registerRuntime(sessionId, info);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
async createSession(docName, mode = 'dedicated', venv = null) {
|
|
595
|
+
return this.createRuntime(docName, mode, venv);
|
|
575
596
|
}
|
|
576
597
|
|
|
577
598
|
// ===========================================================================
|
package/src/shell/styles.js
CHANGED
|
@@ -160,6 +160,24 @@ export const statusBarStyles = `
|
|
|
160
160
|
margin-left: 4px;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
/* Active machine pill (simple status bar mode) */
|
|
164
|
+
.mrmd-statusbar__machine-pill {
|
|
165
|
+
display: inline-flex;
|
|
166
|
+
align-items: center;
|
|
167
|
+
padding: 1px 6px;
|
|
168
|
+
border-radius: 10px;
|
|
169
|
+
border: 1px solid var(--mrmd-border, #333);
|
|
170
|
+
background: var(--mrmd-hover-bg, rgba(255,255,255,0.06));
|
|
171
|
+
color: var(--mrmd-fg, #ccc);
|
|
172
|
+
font-size: 10px;
|
|
173
|
+
line-height: 1.2;
|
|
174
|
+
cursor: pointer;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.mrmd-statusbar__machine-pill:hover {
|
|
178
|
+
border-color: var(--mrmd-accent, #58a6ff);
|
|
179
|
+
}
|
|
180
|
+
|
|
163
181
|
/* Unified files segment - takes more space */
|
|
164
182
|
.mrmd-statusbar__segment--files {
|
|
165
183
|
min-width: 120px;
|
|
@@ -505,6 +523,50 @@ export const filePickerStyles = `
|
|
|
505
523
|
min-height: 300px;
|
|
506
524
|
}
|
|
507
525
|
|
|
526
|
+
/* Machine tab bar */
|
|
527
|
+
.mrmd-filepicker__machines {
|
|
528
|
+
display: flex;
|
|
529
|
+
gap: 4px;
|
|
530
|
+
padding: 6px 0;
|
|
531
|
+
margin-bottom: 4px;
|
|
532
|
+
border-bottom: 1px solid var(--mrmd-border, #333);
|
|
533
|
+
overflow-x: auto;
|
|
534
|
+
white-space: nowrap;
|
|
535
|
+
-webkit-overflow-scrolling: touch;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.mrmd-filepicker__machine-tab {
|
|
539
|
+
display: inline-flex;
|
|
540
|
+
align-items: center;
|
|
541
|
+
gap: 4px;
|
|
542
|
+
padding: 4px 10px;
|
|
543
|
+
border-radius: 12px;
|
|
544
|
+
border: 1px solid var(--mrmd-border, #333);
|
|
545
|
+
background: transparent;
|
|
546
|
+
color: var(--mrmd-fg-muted, #888);
|
|
547
|
+
font-size: var(--mrmd-ui-font-size-sm, 11px);
|
|
548
|
+
font-family: inherit;
|
|
549
|
+
cursor: pointer;
|
|
550
|
+
white-space: nowrap;
|
|
551
|
+
transition: background 0.1s, color 0.1s, border-color 0.1s;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.mrmd-filepicker__machine-tab:hover {
|
|
555
|
+
background: var(--mrmd-hover-bg, rgba(255, 255, 255, 0.08));
|
|
556
|
+
color: var(--mrmd-fg, #ccc);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.mrmd-filepicker__machine-tab--active {
|
|
560
|
+
background: var(--mrmd-selection-bg, rgba(0, 122, 204, 0.2));
|
|
561
|
+
color: var(--mrmd-fg, #ccc);
|
|
562
|
+
border-color: var(--mrmd-accent, #58a6ff);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.mrmd-filepicker__machine-tab--offline {
|
|
566
|
+
opacity: 0.7;
|
|
567
|
+
border-style: dashed;
|
|
568
|
+
}
|
|
569
|
+
|
|
508
570
|
/* Path bar */
|
|
509
571
|
.mrmd-filepicker__path {
|
|
510
572
|
display: flex;
|
|
@@ -601,6 +663,271 @@ export const filePickerStyles = `
|
|
|
601
663
|
}
|
|
602
664
|
`;
|
|
603
665
|
|
|
666
|
+
// =============================================================================
|
|
667
|
+
// MOBILE RESPONSIVE STYLES
|
|
668
|
+
// =============================================================================
|
|
669
|
+
|
|
670
|
+
export const mobileStyles = `
|
|
671
|
+
/* ---- Status Bar: mobile-friendly ---- */
|
|
672
|
+
@media (max-width: 768px) {
|
|
673
|
+
.mrmd-statusbar {
|
|
674
|
+
height: auto;
|
|
675
|
+
min-height: 44px;
|
|
676
|
+
padding: 8px 12px;
|
|
677
|
+
gap: 4px;
|
|
678
|
+
font-size: 13px;
|
|
679
|
+
flex-wrap: wrap;
|
|
680
|
+
padding-bottom: calc(8px + env(safe-area-inset-bottom, 0px));
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.mrmd-statusbar__segment {
|
|
684
|
+
padding: 8px 12px;
|
|
685
|
+
min-height: 44px;
|
|
686
|
+
font-size: 14px;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
.mrmd-statusbar__segment--files {
|
|
690
|
+
min-width: 0;
|
|
691
|
+
flex: 1;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
.mrmd-statusbar__icon {
|
|
695
|
+
width: 18px;
|
|
696
|
+
height: 18px;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
.mrmd-statusbar__dot {
|
|
700
|
+
width: 10px;
|
|
701
|
+
height: 10px;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
.mrmd-statusbar__badge {
|
|
705
|
+
font-size: 10px;
|
|
706
|
+
padding: 2px 6px;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
.mrmd-statusbar__separator {
|
|
710
|
+
display: none;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
.mrmd-statusbar__spacer {
|
|
714
|
+
display: none;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/* ---- Menus → Bottom sheets on mobile ---- */
|
|
719
|
+
@media (max-width: 768px) {
|
|
720
|
+
.mrmd-menu {
|
|
721
|
+
position: fixed !important;
|
|
722
|
+
top: auto !important;
|
|
723
|
+
left: 0 !important;
|
|
724
|
+
right: 0 !important;
|
|
725
|
+
bottom: 0 !important;
|
|
726
|
+
max-width: 100% !important;
|
|
727
|
+
min-width: 100% !important;
|
|
728
|
+
max-height: 70vh;
|
|
729
|
+
border-radius: 16px 16px 0 0;
|
|
730
|
+
box-shadow: 0 -4px 32px rgba(0, 0, 0, 0.4);
|
|
731
|
+
padding: 8px 0;
|
|
732
|
+
padding-bottom: calc(8px + env(safe-area-inset-bottom, 0px));
|
|
733
|
+
animation: mrmd-slide-up 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
@keyframes mrmd-slide-up {
|
|
737
|
+
from { transform: translateY(100%); }
|
|
738
|
+
to { transform: translateY(0); }
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/* Drag handle at top of bottom sheet */
|
|
742
|
+
.mrmd-menu::before {
|
|
743
|
+
content: '';
|
|
744
|
+
display: block;
|
|
745
|
+
width: 36px;
|
|
746
|
+
height: 4px;
|
|
747
|
+
background: var(--mrmd-fg-muted, #666);
|
|
748
|
+
border-radius: 2px;
|
|
749
|
+
margin: 4px auto 8px;
|
|
750
|
+
opacity: 0.4;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
.mrmd-menu__header {
|
|
754
|
+
padding: 12px 20px 8px;
|
|
755
|
+
font-size: 12px;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
.mrmd-menu__item {
|
|
759
|
+
padding: 14px 20px;
|
|
760
|
+
min-height: 48px;
|
|
761
|
+
font-size: 15px;
|
|
762
|
+
gap: 14px;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
.mrmd-menu__item-icon {
|
|
766
|
+
width: 22px;
|
|
767
|
+
height: 22px;
|
|
768
|
+
font-size: 18px;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
.mrmd-menu__item-shortcut {
|
|
772
|
+
display: none; /* Keyboard shortcuts aren't relevant on touch */
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
.mrmd-menu__info {
|
|
776
|
+
padding: 10px 20px;
|
|
777
|
+
font-size: 12px;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
.mrmd-menu__divider {
|
|
781
|
+
margin: 6px 16px;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
.mrmd-menu__file {
|
|
785
|
+
padding: 12px 20px;
|
|
786
|
+
min-height: 48px;
|
|
787
|
+
font-size: 15px;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/* ---- Dialogs: full-screen on mobile ---- */
|
|
792
|
+
@media (max-width: 768px) {
|
|
793
|
+
.mrmd-dialog-overlay {
|
|
794
|
+
padding: 0;
|
|
795
|
+
align-items: flex-end;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
.mrmd-dialog {
|
|
799
|
+
min-width: 100%;
|
|
800
|
+
max-width: 100%;
|
|
801
|
+
max-height: 90vh;
|
|
802
|
+
border-radius: 16px 16px 0 0;
|
|
803
|
+
border: none;
|
|
804
|
+
border-top: 1px solid var(--mrmd-dialog-border, var(--mrmd-border, #454545));
|
|
805
|
+
animation: mrmd-slide-up 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
.mrmd-dialog__header {
|
|
809
|
+
padding: 16px 20px;
|
|
810
|
+
position: relative;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/* Drag handle */
|
|
814
|
+
.mrmd-dialog__header::before {
|
|
815
|
+
content: '';
|
|
816
|
+
position: absolute;
|
|
817
|
+
top: 8px;
|
|
818
|
+
left: 50%;
|
|
819
|
+
transform: translateX(-50%);
|
|
820
|
+
width: 36px;
|
|
821
|
+
height: 4px;
|
|
822
|
+
background: var(--mrmd-fg-muted, #888);
|
|
823
|
+
border-radius: 2px;
|
|
824
|
+
opacity: 0.4;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
.mrmd-dialog__title {
|
|
828
|
+
font-size: 16px;
|
|
829
|
+
padding-top: 8px;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
.mrmd-dialog__close {
|
|
833
|
+
width: 44px;
|
|
834
|
+
height: 44px;
|
|
835
|
+
font-size: 22px;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
.mrmd-dialog__body {
|
|
839
|
+
padding: 16px 20px;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
.mrmd-dialog__footer {
|
|
843
|
+
padding: 16px 20px;
|
|
844
|
+
padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px));
|
|
845
|
+
gap: 12px;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
.mrmd-input {
|
|
849
|
+
padding: 12px 14px;
|
|
850
|
+
font-size: 16px; /* Prevents iOS zoom on focus */
|
|
851
|
+
border-radius: 8px;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
.mrmd-button {
|
|
855
|
+
padding: 12px 20px;
|
|
856
|
+
font-size: 15px;
|
|
857
|
+
border-radius: 8px;
|
|
858
|
+
min-height: 44px;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/* ---- File Picker: full-height on mobile ---- */
|
|
863
|
+
@media (max-width: 768px) {
|
|
864
|
+
.mrmd-filepicker {
|
|
865
|
+
min-height: 50vh;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
.mrmd-filepicker__path {
|
|
869
|
+
padding: 12px 0;
|
|
870
|
+
gap: 2px;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
.mrmd-filepicker__path-segment {
|
|
874
|
+
padding: 6px 10px;
|
|
875
|
+
font-size: 14px;
|
|
876
|
+
min-height: 36px;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
.mrmd-filepicker__item {
|
|
880
|
+
padding: 14px 12px;
|
|
881
|
+
min-height: 52px;
|
|
882
|
+
font-size: 15px;
|
|
883
|
+
gap: 14px;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
.mrmd-filepicker__item-icon {
|
|
887
|
+
font-size: 20px;
|
|
888
|
+
width: 28px;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
.mrmd-filepicker__item-info {
|
|
892
|
+
font-size: 12px;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/* ---- Touch-specific: larger targets for coarse pointers ---- */
|
|
897
|
+
@media (pointer: coarse) {
|
|
898
|
+
.mrmd-statusbar__segment {
|
|
899
|
+
min-height: 44px;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
.mrmd-menu__item {
|
|
903
|
+
min-height: 48px;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
.mrmd-dialog__close {
|
|
907
|
+
min-width: 44px;
|
|
908
|
+
min-height: 44px;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
.mrmd-button {
|
|
912
|
+
min-height: 44px;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
.mrmd-filepicker__item {
|
|
916
|
+
min-height: 48px;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
.mrmd-filepicker__path-segment {
|
|
920
|
+
min-height: 36px;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/* ---- Shared mobile animation keyframes ---- */
|
|
925
|
+
@keyframes mrmd-fade-in {
|
|
926
|
+
from { opacity: 0; }
|
|
927
|
+
to { opacity: 1; }
|
|
928
|
+
}
|
|
929
|
+
`;
|
|
930
|
+
|
|
604
931
|
// =============================================================================
|
|
605
932
|
// ALL STYLES COMBINED
|
|
606
933
|
// =============================================================================
|
|
@@ -672,6 +999,7 @@ ${menuStyles}
|
|
|
672
999
|
${dialogStyles}
|
|
673
1000
|
${filePickerStyles}
|
|
674
1001
|
${studioStyles}
|
|
1002
|
+
${mobileStyles}
|
|
675
1003
|
`;
|
|
676
1004
|
|
|
677
1005
|
// =============================================================================
|
package/src/term-pty-client.js
CHANGED
|
@@ -124,7 +124,20 @@ export class PtyClient {
|
|
|
124
124
|
};
|
|
125
125
|
|
|
126
126
|
this.ws.onmessage = (event) => {
|
|
127
|
-
|
|
127
|
+
// Handle both text and binary messages. Text is the normal case
|
|
128
|
+
// (PTY sends terminal output as text). Binary may arrive if the
|
|
129
|
+
// tunnel proxy incorrectly marks a frame as binary — convert to
|
|
130
|
+
// string so xterm.js can render it (xterm doesn't handle Blobs).
|
|
131
|
+
const data = event.data;
|
|
132
|
+
if (typeof data === 'string') {
|
|
133
|
+
this.config.onData(data);
|
|
134
|
+
} else if (data instanceof Blob) {
|
|
135
|
+
data.text().then(text => this.config.onData(text));
|
|
136
|
+
} else if (data instanceof ArrayBuffer) {
|
|
137
|
+
this.config.onData(new TextDecoder().decode(data));
|
|
138
|
+
} else {
|
|
139
|
+
this.config.onData(data);
|
|
140
|
+
}
|
|
128
141
|
};
|
|
129
142
|
|
|
130
143
|
this.ws.onerror = (event) => {
|
|
@@ -172,16 +185,58 @@ export class PtyClient {
|
|
|
172
185
|
}
|
|
173
186
|
|
|
174
187
|
/**
|
|
175
|
-
* Send data to the PTY (user input)
|
|
188
|
+
* Send data to the PTY (user input).
|
|
189
|
+
* Large payloads (pastes) are chunked to avoid overwhelming the terminal
|
|
190
|
+
* renderer and WebSocket tunnel on markco.dev.
|
|
191
|
+
*
|
|
176
192
|
* @param {string} data - Input data
|
|
177
193
|
*/
|
|
178
194
|
write(data) {
|
|
179
|
-
if (this.ws
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}));
|
|
195
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
196
|
+
|
|
197
|
+
// Small inputs (normal typing, control sequences): send immediately
|
|
198
|
+
if (data.length <= 4096) {
|
|
199
|
+
this.ws.send(JSON.stringify({ type: 'input', data }));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Large paste: chunk it to prevent UI freeze.
|
|
204
|
+
// xterm.js may wrap pastes in bracketed paste markers (\x1b[200~ ... \x1b[201~).
|
|
205
|
+
// We preserve those: send a leading marker once, chunk the body, send trailing marker.
|
|
206
|
+
const BPS = '\x1b[200~';
|
|
207
|
+
const BPE = '\x1b[201~';
|
|
208
|
+
let body = data;
|
|
209
|
+
let hasBracket = false;
|
|
210
|
+
|
|
211
|
+
if (body.startsWith(BPS) && body.endsWith(BPE)) {
|
|
212
|
+
hasBracket = true;
|
|
213
|
+
body = body.slice(BPS.length, -BPE.length);
|
|
184
214
|
}
|
|
215
|
+
|
|
216
|
+
const CHUNK_SIZE = 2048;
|
|
217
|
+
const CHUNK_DELAY_MS = 6;
|
|
218
|
+
let offset = 0;
|
|
219
|
+
const ws = this.ws;
|
|
220
|
+
|
|
221
|
+
if (hasBracket) {
|
|
222
|
+
ws.send(JSON.stringify({ type: 'input', data: BPS }));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const sendNext = () => {
|
|
226
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
227
|
+
if (offset >= body.length) {
|
|
228
|
+
if (hasBracket) {
|
|
229
|
+
ws.send(JSON.stringify({ type: 'input', data: BPE }));
|
|
230
|
+
}
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const chunk = body.slice(offset, offset + CHUNK_SIZE);
|
|
234
|
+
offset += CHUNK_SIZE;
|
|
235
|
+
ws.send(JSON.stringify({ type: 'input', data: chunk }));
|
|
236
|
+
setTimeout(sendNext, CHUNK_DELAY_MS);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
sendNext();
|
|
185
240
|
}
|
|
186
241
|
|
|
187
242
|
/**
|