mrmd-editor 0.5.0 → 0.7.0

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/src/index.js CHANGED
@@ -304,55 +304,55 @@ function parseProgress(output) {
304
304
  function createJavaScriptRuntime(options = {}) {
305
305
  const rt = createMrmdJsRuntime(options);
306
306
 
307
- // Track named sessions - each can have different isolation
308
- // Session naming:
309
- // null/undefined/'default'/'main'/'none' → main context (no isolation)
307
+ // Track named execution contexts - each can have different isolation
308
+ // Context naming:
309
+ // null/undefined/'default'/'main'/'none' → configured default isolation
310
310
  // 'sandbox'/'iframe' → sandboxed iframe
311
311
  // other names → sandboxed iframe with separate scope
312
- const sessions = new Map();
312
+ const contexts = new Map();
313
313
  const defaultIsolation = options.defaultIsolation || 'iframe';
314
314
 
315
315
  /**
316
- * Get or create a session by name
317
- * @param {string|null} sessionName
316
+ * Get or create a context by name
317
+ * @param {string|null} contextName
318
318
  * @returns {Session}
319
319
  */
320
- function getOrCreateSession(sessionName) {
321
- // Normalize session name
322
- const name = sessionName || 'default';
320
+ function getOrCreateContext(contextName) {
321
+ // Normalize context name
322
+ const name = contextName || 'default';
323
323
 
324
- // Return existing session
325
- if (sessions.has(name)) {
326
- return sessions.get(name);
324
+ // Return existing context
325
+ if (contexts.has(name)) {
326
+ return contexts.get(name);
327
327
  }
328
328
 
329
- // Determine isolation mode based on session name
329
+ // Determine isolation mode based on context name
330
330
  let isolation;
331
- if (!sessionName || sessionName === 'default' || sessionName === 'main' || sessionName === 'none') {
332
- // Default session uses the configured default isolation
331
+ if (!contextName || contextName === 'default' || contextName === 'main' || contextName === 'none') {
332
+ // Default context uses the configured default isolation
333
333
  isolation = defaultIsolation;
334
- } else if (sessionName === 'sandbox' || sessionName === 'iframe') {
334
+ } else if (contextName === 'sandbox' || contextName === 'iframe') {
335
335
  // Explicit sandbox request
336
336
  isolation = 'iframe';
337
337
  } else {
338
- // Named sessions are sandboxed by default (separate scope)
338
+ // Named contexts are sandboxed by default (separate scope)
339
339
  isolation = 'iframe';
340
340
  }
341
341
 
342
- // Create new session with appropriate isolation
343
- const session = rt.createSession({
342
+ // Create new execution context with appropriate isolation
343
+ const context = rt.createSession({
344
344
  language: 'javascript',
345
345
  isolation,
346
346
  id: name,
347
347
  });
348
348
 
349
- sessions.set(name, session);
350
- console.log(`[JS Runtime] Created session '${name}' with isolation: ${isolation}`);
351
- return session;
349
+ contexts.set(name, context);
350
+ console.log(`[JS Runtime] Created context '${name}' with isolation: ${isolation}`);
351
+ return context;
352
352
  }
353
353
 
354
- // Create default session eagerly
355
- const defaultSession = getOrCreateSession('default');
354
+ // Create default context eagerly
355
+ const defaultContext = getOrCreateContext('default');
356
356
 
357
357
  // Languages supported by mrmd-js
358
358
  const supportedLanguages = {
@@ -379,8 +379,9 @@ function createJavaScriptRuntime(options = {}) {
379
379
  /** Execute code (non-streaming) */
380
380
  async execute(code, language, execOptions = {}) {
381
381
  const lang = supportedLanguages[language.toLowerCase()] || 'javascript';
382
- const session = getOrCreateSession(execOptions.session);
383
- const result = await session.execute(code, { language: lang });
382
+ const contextName = execOptions.context ?? execOptions.session;
383
+ const context = getOrCreateContext(contextName);
384
+ const result = await context.execute(code, { language: lang });
384
385
  return {
385
386
  success: result.success,
386
387
  stdout: result.stdout || '',
@@ -394,8 +395,9 @@ function createJavaScriptRuntime(options = {}) {
394
395
  /** Execute code with streaming output */
395
396
  async executeStreaming(code, language, onChunk, onStdinRequest, execOptions = {}) {
396
397
  const lang = supportedLanguages[language.toLowerCase()] || 'javascript';
397
- const session = getOrCreateSession(execOptions.session);
398
- const result = await session.execute(code, { language: lang });
398
+ const contextName = execOptions.context ?? execOptions.session;
399
+ const context = getOrCreateContext(contextName);
400
+ const result = await context.execute(code, { language: lang });
399
401
 
400
402
  // Handle different output types
401
403
  let output = result.stdout || '';
@@ -407,6 +409,9 @@ function createJavaScriptRuntime(options = {}) {
407
409
  // Prefer HTML representation
408
410
  if (display.data['text/html']) {
409
411
  output = display.data['text/html'];
412
+ } else if (display.data['text/css']) {
413
+ // Preserve CSS source so the output widget can show selector impact
414
+ output = display.data['text/css'];
410
415
  } else if (display.data['text/plain']) {
411
416
  output = display.data['text/plain'];
412
417
  }
@@ -431,18 +436,18 @@ function createJavaScriptRuntime(options = {}) {
431
436
  };
432
437
  },
433
438
 
434
- /** Reset a session (clear all variables) */
435
- reset(sessionName) {
436
- const session = sessions.get(sessionName || 'default');
437
- if (session) {
438
- session.reset();
439
+ /** Reset a context (clear all variables) */
440
+ reset(contextName) {
441
+ const context = contexts.get(contextName || 'default');
442
+ if (context) {
443
+ context.reset();
439
444
  }
440
445
  },
441
446
 
442
- /** Reset all sessions */
447
+ /** Reset all contexts */
443
448
  resetAll() {
444
- for (const session of sessions.values()) {
445
- session.reset();
449
+ for (const context of contexts.values()) {
450
+ context.reset();
446
451
  }
447
452
  },
448
453
 
@@ -451,23 +456,32 @@ function createJavaScriptRuntime(options = {}) {
451
456
  return rt;
452
457
  },
453
458
 
454
- /** Get a session by name (default if not specified) */
455
- getSession(sessionName) {
456
- return getOrCreateSession(sessionName);
459
+ /** Get a context by name (default if not specified) */
460
+ getContext(contextName) {
461
+ return getOrCreateContext(contextName);
462
+ },
463
+
464
+ /** List all context names */
465
+ listContexts() {
466
+ return Array.from(contexts.keys());
467
+ },
468
+
469
+ // Legacy aliases (kept for compatibility inside monorepo)
470
+ getSession(contextName) {
471
+ return getOrCreateContext(contextName);
457
472
  },
458
473
 
459
- /** List all session names */
460
474
  listSessions() {
461
- return Array.from(sessions.keys());
475
+ return Array.from(contexts.keys());
462
476
  },
463
477
 
464
- /** Destroy the runtime and all sessions */
478
+ /** Destroy the runtime and all contexts */
465
479
  destroy() {
466
480
  rt.destroy();
467
481
  },
468
482
 
469
483
  // =========================================================================
470
- // LSP Features (powered by mrmd-js session)
484
+ // LSP Features (powered by mrmd-js default context)
471
485
  // =========================================================================
472
486
 
473
487
  /**
@@ -479,7 +493,7 @@ function createJavaScriptRuntime(options = {}) {
479
493
  * @returns {{found: boolean, name?: string, type?: string, value?: string, signature?: string}|null}
480
494
  */
481
495
  hover(code, cursor) {
482
- return defaultSession.hover(code, cursor);
496
+ return defaultContext.hover(code, cursor);
483
497
  },
484
498
 
485
499
  /**
@@ -491,7 +505,7 @@ function createJavaScriptRuntime(options = {}) {
491
505
  * @returns {{matches: Array, cursorStart: number, cursorEnd: number}}
492
506
  */
493
507
  complete(code, cursor) {
494
- return defaultSession.complete(code, cursor);
508
+ return defaultContext.complete(code, cursor);
495
509
  },
496
510
 
497
511
  /**
@@ -503,16 +517,18 @@ function createJavaScriptRuntime(options = {}) {
503
517
  * @returns {Object|null}
504
518
  */
505
519
  inspect(code, cursor, options = {}) {
506
- return defaultSession.inspect(code, cursor, options);
520
+ return defaultContext.inspect(code, cursor, options);
507
521
  },
508
522
 
509
523
  /**
510
- * List all variables in the session namespace.
524
+ * List all variables in a context namespace.
511
525
  *
526
+ * @param {Object} [filter]
527
+ * @param {string} [contextName='default']
512
528
  * @returns {Array<{name: string, type: string, value: string, expandable?: boolean}>}
513
529
  */
514
- listVariables() {
515
- return defaultSession.listVariables();
530
+ listVariables(filter = {}, contextName = 'default') {
531
+ return getOrCreateContext(contextName).listVariables(filter);
516
532
  },
517
533
 
518
534
  /**
@@ -523,7 +539,7 @@ function createJavaScriptRuntime(options = {}) {
523
539
  * @returns {Object}
524
540
  */
525
541
  getVariable(name, options = {}) {
526
- return defaultSession.getVariable(name, options);
542
+ return defaultContext.getVariable(name, options);
527
543
  },
528
544
 
529
545
  /**
@@ -533,7 +549,7 @@ function createJavaScriptRuntime(options = {}) {
533
549
  * @returns {{status: 'complete'|'incomplete'|'invalid'|'unknown', indent?: string}}
534
550
  */
535
551
  isComplete(code) {
536
- return defaultSession.isComplete(code);
552
+ return defaultContext.isComplete(code);
537
553
  },
538
554
 
539
555
  /**
@@ -543,7 +559,7 @@ function createJavaScriptRuntime(options = {}) {
543
559
  * @returns {Promise<{formatted: string, changed: boolean}>}
544
560
  */
545
561
  format(code) {
546
- return defaultSession.format(code);
562
+ return defaultContext.format(code);
547
563
  },
548
564
 
549
565
  /**
@@ -551,7 +567,7 @@ function createJavaScriptRuntime(options = {}) {
551
567
  * @returns {import('./runtime-lsp.js').RuntimeLSPProvider}
552
568
  */
553
569
  getLSPProvider() {
554
- return adaptMrmdJsSession(defaultSession);
570
+ return adaptMrmdJsSession(defaultContext);
555
571
  },
556
572
  };
557
573
  }
@@ -726,6 +742,15 @@ const codeBlockStyles = EditorView.theme({
726
742
  '.cm-codeblock-fence::selection, .cm-codeblock-fence *::selection': {
727
743
  backgroundColor: 'var(--editor-selection, #264f78) !important',
728
744
  },
745
+ // Mobile: code blocks need to be larger and scroll horizontally
746
+ '@media (max-width: 768px)': {
747
+ '.cm-codeblock-line': {
748
+ fontSize: 'max(var(--code-font-size, 0.8em), 13px)',
749
+ },
750
+ '.cm-codeblock-fence': {
751
+ fontSize: '0.6em', // Slightly larger than desktop's 0.5em for visibility
752
+ },
753
+ },
729
754
  });
730
755
  // #endregion CODE_BLOCK_BACKGROUND
731
756
 
@@ -823,8 +848,6 @@ function create(target, options = {}) {
823
848
  // MRP and builtin types are handled separately below
824
849
  }
825
850
 
826
- // JavaScript runtime option (backward compat)
827
- const javascript = options.javascript !== undefined ? options.javascript : true;
828
851
  // JavaScript isolation mode: 'iframe' (default, sandboxed) or 'none' (main window context)
829
852
  const javascriptIsolation = options.javascriptIsolation || 'iframe';
830
853
 
@@ -893,6 +916,11 @@ function create(target, options = {}) {
893
916
  '.cm-gutters': { display: 'none' },
894
917
  '.cm-activeLineGutter': { backgroundColor: 'transparent' },
895
918
  '&.cm-focused': { outline: 'none' },
919
+ // Mobile: slightly larger text, comfortable line-height
920
+ '@media (max-width: 768px)': {
921
+ '&': { fontSize: '17px' },
922
+ '.cm-scroller': { lineHeight: '1.7' },
923
+ },
896
924
  });
897
925
 
898
926
  // Inject CSS styles
@@ -1021,6 +1049,7 @@ function create(target, options = {}) {
1021
1049
  // Event handlers
1022
1050
  const changeHandlers = [];
1023
1051
  const saveHandlers = [];
1052
+ const frontmatterTitleCommitHandlers = [];
1024
1053
  const viewSourceHandlers = [];
1025
1054
  const cellRunHandlers = [];
1026
1055
  const cellOutputHandlers = [];
@@ -1036,6 +1065,19 @@ function create(target, options = {}) {
1036
1065
  }
1037
1066
  });
1038
1067
 
1068
+ const handleFrontmatterTitleCommit = (event) => {
1069
+ const title = event?.detail?.title;
1070
+ if (!title) return;
1071
+ for (const handler of frontmatterTitleCommitHandlers) {
1072
+ try {
1073
+ handler(title, event.detail || {});
1074
+ } catch (err) {
1075
+ console.warn('[mrmd] frontmatter title commit handler failed:', err);
1076
+ }
1077
+ }
1078
+ };
1079
+ element.addEventListener('mrmd:frontmatter-title-commit', handleFrontmatterTitleCommit);
1080
+
1039
1081
  // Create runtime registry
1040
1082
  const registry = createRuntimeRegistry();
1041
1083
 
@@ -1044,19 +1086,17 @@ function create(target, options = {}) {
1044
1086
  registry.register(name, runtime);
1045
1087
  }
1046
1088
 
1047
- // Built-in JavaScript runtime (mrmd-js)
1048
- // - true: use built-in mrmd-js runtime (default)
1049
- // - false: disable JavaScript execution
1050
- // - object: use custom runtime (must implement supports/execute/executeStreaming)
1089
+ // JavaScript runtime for editor + LSP features.
1090
+ // Use normalized config so options.runtimes.javascript is not shadowed by a second runtime.
1051
1091
  let jsRuntime = null;
1052
- if (javascript === true) {
1092
+ const jsRuntimeConfig = config.runtimes.javascript;
1093
+ if (jsRuntimeConfig?.type === 'builtin') {
1053
1094
  jsRuntime = createJavaScriptRuntime({ defaultIsolation: javascriptIsolation });
1054
1095
  registry.register('javascript', jsRuntime);
1055
- } else if (javascript && typeof javascript === 'object') {
1056
- // Custom runtime provided
1057
- registry.register('javascript', javascript);
1096
+ } else if (jsRuntimeConfig?.type === 'custom' && jsRuntimeConfig.instance) {
1097
+ jsRuntime = jsRuntimeConfig.instance;
1058
1098
  }
1059
- // javascript === false means no JS runtime
1099
+ // If javascript is disabled, jsRuntime remains null.
1060
1100
 
1061
1101
  // =========================================================================
1062
1102
  // RUNTIME LSP PROVIDERS
@@ -1066,7 +1106,7 @@ function create(target, options = {}) {
1066
1106
  const runtimeLspProviders = new Map();
1067
1107
 
1068
1108
  // Add JS runtime LSP provider if available
1069
- if (jsRuntime) {
1109
+ if (jsRuntime?.getLSPProvider) {
1070
1110
  const jsProvider = jsRuntime.getLSPProvider();
1071
1111
  runtimeLspProviders.set('javascript', jsProvider);
1072
1112
  }
@@ -2031,17 +2071,16 @@ function create(target, options = {}) {
2031
2071
  * Refresh variables from all MRP runtimes
2032
2072
  * Fetches current variable state and updates state.variables
2033
2073
  *
2034
- * @param {string} [sessionId] - Specific session to refresh (optional)
2035
2074
  * @returns {Promise<void>}
2036
2075
  */
2037
- async refreshVariables(sessionId) {
2076
+ async refreshVariables() {
2038
2077
  for (const [name, runtime] of registry.runtimes) {
2039
2078
  // Check if runtime is an MRP client (has getVariables method)
2040
2079
  if (typeof runtime.getVariables === 'function') {
2041
2080
  try {
2042
- const result = await runtime.getVariables(sessionId);
2081
+ const result = await runtime.getVariables();
2043
2082
  if (result && result.variables) {
2044
- const session = sessionId || 'default';
2083
+ const session = 'default';
2045
2084
  const variables = {};
2046
2085
  for (const v of result.variables) {
2047
2086
  variables[v.name] = {
@@ -2106,6 +2145,14 @@ function create(target, options = {}) {
2106
2145
  };
2107
2146
  },
2108
2147
 
2148
+ onFrontmatterTitleCommit(callback) {
2149
+ frontmatterTitleCommitHandlers.push(callback);
2150
+ return () => {
2151
+ const idx = frontmatterTitleCommitHandlers.indexOf(callback);
2152
+ if (idx >= 0) frontmatterTitleCommitHandlers.splice(idx, 1);
2153
+ };
2154
+ },
2155
+
2109
2156
  onCellRun(callback) {
2110
2157
  return this.execution.on('cellRun', callback);
2111
2158
  },
@@ -2179,6 +2226,7 @@ function create(target, options = {}) {
2179
2226
  }
2180
2227
  // Clean up theme watcher
2181
2228
  unwatchTheme();
2229
+ element.removeEventListener('mrmd:frontmatter-title-commit', handleFrontmatterTitleCommit);
2182
2230
  // Clean up undo manager
2183
2231
  undoManager.destroy();
2184
2232
  view.destroy();
@@ -2522,12 +2570,12 @@ function create(target, options = {}) {
2522
2570
  }
2523
2571
  // #endregion CREATE
2524
2572
 
2525
- // #region SESSION
2573
+ // #region RUNTIME
2526
2574
  /**
2527
- * Create an editor session via orchestrator.
2575
+ * Create an editor runtime attachment via orchestrator.
2528
2576
  *
2529
2577
  * This is the simplest way to use mrmd with full features:
2530
- * - Automatically creates session with orchestrator
2578
+ * - Automatically creates/attaches runtime with orchestrator
2531
2579
  * - Connects to sync server
2532
2580
  * - Sets up Python runtime (shared or dedicated)
2533
2581
  * - Enables monitor mode for long-running executions
@@ -2538,28 +2586,28 @@ function create(target, options = {}) {
2538
2586
  * @param {string} options.doc - Document name (required)
2539
2587
  * @param {string} [options.python='shared'] - 'shared' or 'dedicated'
2540
2588
  * @param {Object} [options.editor] - Additional editor options
2541
- * @returns {Promise<Object>} Editor instance with destroySession() method
2589
+ * @returns {Promise<Object>} Editor instance with destroyRuntime() method
2542
2590
  *
2543
2591
  * @example
2544
2592
  * // Basic usage
2545
- * const editor = await mrmd.session('http://localhost:8080', '#editor', {
2593
+ * const editor = await mrmd.runtime('http://localhost:8080', '#editor', {
2546
2594
  * doc: 'my-notebook',
2547
2595
  * });
2548
2596
  *
2549
2597
  * // With dedicated Python runtime
2550
- * const editor = await mrmd.session('http://localhost:8080', '#editor', {
2598
+ * const editor = await mrmd.runtime('http://localhost:8080', '#editor', {
2551
2599
  * doc: 'my-notebook',
2552
2600
  * python: 'dedicated',
2553
2601
  * });
2554
2602
  *
2555
2603
  * // Clean up when done
2556
- * await editor.destroySession();
2604
+ * await editor.destroyRuntime();
2557
2605
  */
2558
- async function session(orchestratorUrl, target, options = {}) {
2606
+ async function runtime(orchestratorUrl, target, options = {}) {
2559
2607
  const { doc, python = 'shared', editor: editorOptions = {} } = options;
2560
2608
 
2561
2609
  if (!doc) {
2562
- throw new Error('mrmd.session: doc option is required');
2610
+ throw new Error('mrmd.runtime: doc option is required');
2563
2611
  }
2564
2612
 
2565
2613
  // Normalize orchestrator URL
@@ -2568,24 +2616,33 @@ async function session(orchestratorUrl, target, options = {}) {
2568
2616
  baseUrl = baseUrl.slice(0, -1);
2569
2617
  }
2570
2618
 
2571
- // Create session with orchestrator
2572
- console.log(`[mrmd.session] Creating session for '${doc}' (python=${python})`);
2619
+ // Create runtime attachment with orchestrator
2620
+ console.log(`[mrmd.runtime] Creating runtime for '${doc}' (python=${python})`);
2573
2621
 
2574
- const response = await fetch(`${baseUrl}/api/sessions`, {
2622
+ // Prefer /api/runtimes, fall back to /api/sessions for legacy orchestrators
2623
+ let response = await fetch(`${baseUrl}/api/runtimes`, {
2575
2624
  method: 'POST',
2576
2625
  headers: { 'Content-Type': 'application/json' },
2577
2626
  body: JSON.stringify({ doc, python }),
2578
2627
  });
2579
2628
 
2629
+ if (!response.ok && response.status === 404) {
2630
+ response = await fetch(`${baseUrl}/api/sessions`, {
2631
+ method: 'POST',
2632
+ headers: { 'Content-Type': 'application/json' },
2633
+ body: JSON.stringify({ doc, python }),
2634
+ });
2635
+ }
2636
+
2580
2637
  if (!response.ok) {
2581
2638
  const error = await response.text();
2582
- throw new Error(`Failed to create session: ${error}`);
2639
+ throw new Error(`Failed to create runtime attachment: ${error}`);
2583
2640
  }
2584
2641
 
2585
2642
  const sessionInfo = await response.json();
2586
- console.log('[mrmd.session] Session created:', sessionInfo);
2643
+ console.log('[mrmd.runtime] Runtime created:', sessionInfo);
2587
2644
 
2588
- // Extract URLs from session info
2645
+ // Extract URLs from response
2589
2646
  const syncUrl = sessionInfo.sync;
2590
2647
  const runtimeUrl = sessionInfo.runtimes?.python?.url;
2591
2648
 
@@ -2610,17 +2667,16 @@ async function session(orchestratorUrl, target, options = {}) {
2610
2667
  ydoc: editor.ydoc,
2611
2668
  runtimeUrl,
2612
2669
  awareness: editor.awareness,
2613
- session: doc,
2614
2670
  });
2615
2671
  }
2616
2672
 
2617
- // Store session info on editor
2673
+ // Store runtime info on editor
2618
2674
  editor._sessionInfo = sessionInfo;
2619
2675
  editor._orchestratorUrl = baseUrl;
2620
2676
 
2621
- // Add destroySession method
2622
- editor.destroySession = async function() {
2623
- console.log(`[mrmd.session] Destroying session for '${doc}'`);
2677
+ // Add destroyRuntime method
2678
+ editor.destroyRuntime = async function() {
2679
+ console.log(`[mrmd.runtime] Destroying runtime for '${doc}'`);
2624
2680
 
2625
2681
  // Disconnect from sync
2626
2682
  if (editor.disconnect) {
@@ -2629,28 +2685,33 @@ async function session(orchestratorUrl, target, options = {}) {
2629
2685
 
2630
2686
  // Call orchestrator to clean up
2631
2687
  try {
2632
- const resp = await fetch(`${baseUrl}/api/sessions/${encodeURIComponent(doc)}`, {
2688
+ let resp = await fetch(`${baseUrl}/api/runtimes/${encodeURIComponent(doc)}`, {
2633
2689
  method: 'DELETE',
2634
2690
  });
2691
+ if (!resp.ok && resp.status === 404) {
2692
+ resp = await fetch(`${baseUrl}/api/sessions/${encodeURIComponent(doc)}`, {
2693
+ method: 'DELETE',
2694
+ });
2695
+ }
2635
2696
  if (!resp.ok) {
2636
- console.warn(`[mrmd.session] Failed to destroy session: ${resp.statusText}`);
2697
+ console.warn(`[mrmd.runtime] Failed to destroy runtime: ${resp.statusText}`);
2637
2698
  }
2638
2699
  } catch (err) {
2639
- console.warn(`[mrmd.session] Failed to destroy session:`, err);
2700
+ console.warn(`[mrmd.runtime] Failed to destroy runtime:`, err);
2640
2701
  }
2641
2702
 
2642
2703
  // Destroy editor
2643
2704
  editor.destroy();
2644
2705
  };
2645
2706
 
2646
- // Add method to get session info
2647
- editor.getSessionInfo = function() {
2707
+ // Add method to get runtime info
2708
+ editor.getRuntimeInfo = function() {
2648
2709
  return this._sessionInfo;
2649
2710
  };
2650
2711
 
2651
2712
  return editor;
2652
2713
  }
2653
- // #endregion SESSION
2714
+ // #endregion RUNTIME
2654
2715
 
2655
2716
  // #region DRIVE
2656
2717
  /**
@@ -3014,7 +3075,7 @@ const mrmd = {
3014
3075
  version: VERSION,
3015
3076
  create,
3016
3077
  drive,
3017
- session,
3078
+ runtime,
3018
3079
  yjs,
3019
3080
  codemirror,
3020
3081
  terminal,
@@ -3072,7 +3133,7 @@ export default mrmd;
3072
3133
  export {
3073
3134
  create,
3074
3135
  drive,
3075
- session,
3136
+ runtime,
3076
3137
  yjs,
3077
3138
  codemirror,
3078
3139
  terminal,
@@ -3208,4 +3269,8 @@ export {
3208
3269
 
3209
3270
  // Re-export shell components for direct imports
3210
3271
  export const { createStudio, OrchestratorClient, Drive, createDrive, ShellStateManager, injectShellStyles } = shellModule;
3272
+
3273
+ // Document language detection and frontmatter updater
3274
+ export { getDocumentLanguages, getLanguageDisplay, isExecutableLanguage } from './document-languages.js';
3275
+ export { parseFrontmatter, updateFrontmatterSession, readFrontmatterSession, getEffectiveSessionConfig } from './frontmatter-updater.js';
3211
3276
  // #endregion EXPORTS