@marimo-team/islands 0.23.9-dev9 → 0.23.10-dev0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/{ConnectedDataExplorerComponent-OzrfMM5L.js → ConnectedDataExplorerComponent-CyV83R2m.js} +4 -4
  2. package/dist/assets/__vite-browser-external-Ci2ZQfXU.js +1 -0
  3. package/dist/assets/{worker-CpBbwbQo.js → worker-ip3AI_sN.js} +2 -2
  4. package/dist/{chat-ui-BDI3FMI8.js → chat-ui-ChD4VvCo.js} +3060 -3033
  5. package/dist/{code-visibility-DgHF4q8X.js → code-visibility-CjGICDxg.js} +1368 -1204
  6. package/dist/{formats-DQ5qjo_Q.js → formats-DHxc-FdY.js} +1 -1
  7. package/dist/{glide-data-editor-DqRY9naW.js → glide-data-editor-BOmK9ETQ.js} +2 -2
  8. package/dist/{html-to-image-CiSinpSR.js → html-to-image-BHv7CEU_.js} +2145 -2153
  9. package/dist/{input-CZD2z6X2.js → input-_2sjvfne.js} +1 -1
  10. package/dist/main.js +680 -705
  11. package/dist/{mermaid-IU93XzmY.js → mermaid-lXOw5Py9.js} +2 -2
  12. package/dist/{process-output-5qJjMRKh.js → process-output-BvySRgli.js} +33 -25
  13. package/dist/{reveal-component-qpHJES_u.js → reveal-component-DVWED--8.js} +312 -291
  14. package/dist/{spec-a6DaqW__.js → spec-B96zNUEA.js} +1 -1
  15. package/dist/style.css +1 -1
  16. package/dist/{toDate-ZVVIBmdk.js → toDate-x-WRDCH7.js} +1 -1
  17. package/dist/{useAsyncData-C008zUPi.js → useAsyncData-iRgKDT5s.js} +1 -1
  18. package/dist/{useDeepCompareMemoize-BrA3_n61.js → useDeepCompareMemoize-CkQ57VS2.js} +1 -1
  19. package/dist/{useLifecycle-BNaoJ5a4.js → useLifecycle-BBO9PIph.js} +1 -1
  20. package/dist/{useTheme-7O0YWlE5.js → useTheme-DHIrRQOe.js} +34 -21
  21. package/dist/{vega-component-DJNmOdUj.js → vega-component-Dq-SH463.js} +5 -5
  22. package/package.json +1 -1
  23. package/src/components/ai/__tests__/ai-utils.test.ts +43 -38
  24. package/src/components/ai/ai-model-dropdown.tsx +2 -2
  25. package/src/components/app-config/ai-config.tsx +147 -16
  26. package/src/components/app-config/user-config-form.tsx +37 -1
  27. package/src/components/chat/__tests__/chat-utils.test.ts +269 -0
  28. package/src/components/chat/chat-panel.tsx +38 -5
  29. package/src/components/chat/chat-utils.ts +14 -58
  30. package/src/components/data-table/TableBottomBar.tsx +5 -8
  31. package/src/components/data-table/__tests__/column-explorer.test.tsx +128 -0
  32. package/src/components/data-table/__tests__/header-items.test.tsx +220 -10
  33. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +95 -29
  34. package/src/components/data-table/column-header.tsx +17 -12
  35. package/src/components/data-table/data-table.tsx +4 -0
  36. package/src/components/data-table/export-actions.tsx +19 -12
  37. package/src/components/data-table/header-items.tsx +40 -16
  38. package/src/components/data-table/hooks/use-column-visibility.ts +14 -0
  39. package/src/components/data-table/schemas.ts +2 -2
  40. package/src/components/data-table/table-explorer-panel/table-explorer-panel.tsx +16 -6
  41. package/src/components/databases/display.tsx +2 -0
  42. package/src/components/datasources/__tests__/utils.test.ts +82 -0
  43. package/src/components/datasources/utils.ts +16 -15
  44. package/src/components/editor/Disconnected.tsx +1 -60
  45. package/src/components/editor/__tests__/viewer-banner.test.tsx +89 -0
  46. package/src/components/editor/actions/pair-with-agent-modal.tsx +1 -0
  47. package/src/components/editor/actions/useCellActionButton.tsx +3 -3
  48. package/src/components/editor/actions/useNotebookActions.tsx +5 -2
  49. package/src/components/editor/cell/code/cell-editor.tsx +25 -5
  50. package/src/components/editor/chrome/types.ts +13 -6
  51. package/src/components/editor/chrome/wrapper/app-chrome.tsx +6 -4
  52. package/src/components/editor/chrome/wrapper/footer-items/ai-status.tsx +10 -1
  53. package/src/components/editor/chrome/wrapper/sidebar.tsx +7 -5
  54. package/src/components/editor/errors/auto-fix.tsx +3 -3
  55. package/src/components/editor/header/__tests__/status.test.tsx +0 -15
  56. package/src/components/editor/header/app-header.tsx +1 -4
  57. package/src/components/editor/header/status.tsx +4 -13
  58. package/src/components/editor/navigation/__tests__/navigation.test.ts +15 -0
  59. package/src/components/editor/navigation/navigation.ts +5 -0
  60. package/src/components/editor/output/MarimoErrorOutput.tsx +103 -25
  61. package/src/components/editor/output/MarimoTracebackOutput.tsx +28 -39
  62. package/src/components/editor/renderers/cell-array.tsx +27 -24
  63. package/src/components/editor/renderers/slides-layout/__tests__/compute-slide-cells.test.ts +30 -17
  64. package/src/components/editor/renderers/slides-layout/compute-slide-cells.ts +17 -8
  65. package/src/components/editor/renderers/slides-layout/slides-layout.tsx +10 -12
  66. package/src/components/editor/viewer-banner.tsx +82 -0
  67. package/src/components/slides/minimap.tsx +45 -9
  68. package/src/components/slides/reveal-component.tsx +82 -37
  69. package/src/components/slides/slide-cell-view.tsx +12 -1
  70. package/src/components/slides/slide-form.tsx +11 -3
  71. package/src/components/static-html/static-banner.tsx +28 -22
  72. package/src/core/ai/__tests__/model-registry.test.ts +72 -60
  73. package/src/core/ai/model-registry.ts +33 -28
  74. package/src/core/cells/__tests__/actions.test.ts +48 -0
  75. package/src/core/cells/actions.ts +5 -6
  76. package/src/core/codemirror/__tests__/setup.test.ts +29 -0
  77. package/src/core/codemirror/cells/traceback-decorations.ts +1 -1
  78. package/src/core/codemirror/cm.ts +50 -3
  79. package/src/core/codemirror/completion/hints.ts +4 -1
  80. package/src/core/codemirror/format.ts +1 -0
  81. package/src/core/codemirror/keymaps/vim.ts +63 -0
  82. package/src/core/codemirror/language/languages/sql/sql.ts +1 -0
  83. package/src/core/codemirror/language/languages/sql/utils.ts +2 -0
  84. package/src/core/config/__tests__/config-schema.test.ts +4 -0
  85. package/src/core/config/config-schema.ts +4 -0
  86. package/src/core/config/config.ts +16 -0
  87. package/src/core/edit-app.tsx +3 -0
  88. package/src/core/islands/bootstrap.ts +2 -0
  89. package/src/core/kernel/__tests__/handlers.test.ts +5 -0
  90. package/src/core/websocket/__tests__/useMarimoKernelConnection.test.ts +0 -13
  91. package/src/core/websocket/types.ts +0 -6
  92. package/src/core/websocket/useMarimoKernelConnection.tsx +3 -12
  93. package/src/css/app/Cell.css +0 -1
  94. package/src/plugins/impl/DataTablePlugin.tsx +48 -22
  95. package/src/plugins/impl/chat/ChatPlugin.tsx +7 -1
  96. package/src/plugins/impl/chat/__tests__/chat-ui.test.ts +278 -0
  97. package/src/plugins/impl/chat/chat-ui.tsx +106 -59
  98. package/src/plugins/impl/chat/types.ts +5 -0
  99. package/src/utils/__tests__/json-parser.test.ts +1 -69
  100. package/src/utils/json/json-parser.ts +0 -30
  101. package/dist/assets/__vite-browser-external-CAdMKBac.js +0 -1
@@ -66,6 +66,7 @@ interface SlideThumbnailCardProps extends React.HTMLAttributes<HTMLDivElement> {
66
66
  isActiveDragSource?: boolean;
67
67
  isOverlay?: boolean;
68
68
  isVisible?: boolean;
69
+ isNoOutput?: boolean;
69
70
  slideType?: SlideType;
70
71
  ref?: React.Ref<HTMLDivElement>;
71
72
  }
@@ -77,6 +78,7 @@ interface SlideThumbnailRowProps extends React.ButtonHTMLAttributes<HTMLButtonEl
77
78
  dropIndicator?: DropPosition | null;
78
79
  isActiveDragSource?: boolean;
79
80
  isVisible?: boolean;
81
+ isNoOutput?: boolean;
80
82
  slideType?: SlideType;
81
83
  ref?: React.Ref<HTMLButtonElement>;
82
84
  }
@@ -86,15 +88,23 @@ interface SlidesMinimapProps {
86
88
  thumbnailWidth: number;
87
89
  canReorder: boolean;
88
90
  activeCellId: CellId | null;
89
- // Set of cell ids that are marked `skip` in the slides layout.
91
+ // Set of cell ids that should be visually treated like skipped entries.
90
92
  skippedIds?: ReadonlySet<CellId>;
93
+ // Set of cell ids that currently have no rendered output.
94
+ noOutputIds?: ReadonlySet<CellId>;
91
95
  slideTypes?: ReadonlyMap<CellId, SlideType>;
92
96
  onSlideClick: (index: number) => void;
93
97
  }
94
98
 
99
+ interface ThumbnailVisual {
100
+ label: string;
101
+ description: string;
102
+ Icon?: LucideIcon;
103
+ }
104
+
95
105
  function getSlideTypeVisual(
96
106
  slideType: SlideType | undefined,
97
- ): { label: string; description: string; Icon: LucideIcon } | null {
107
+ ): ThumbnailVisual | null {
98
108
  if (!slideType || slideType === "slide") {
99
109
  return null;
100
110
  }
@@ -102,8 +112,13 @@ function getSlideTypeVisual(
102
112
  return { label, description, Icon };
103
113
  }
104
114
 
115
+ const NO_OUTPUT_VISUAL: ThumbnailVisual = {
116
+ label: "No output",
117
+ description: "Hidden because this cell has no output.",
118
+ };
119
+
105
120
  const SLIDE_ASPECT_RATIO = 16 / 9;
106
- const SLIDE_BASE_WIDTH = 960;
121
+ const SLIDE_BASE_WIDTH = 520;
107
122
 
108
123
  function computeThumbnailDimensions(width: number): ThumbnailDimensions {
109
124
  return {
@@ -196,6 +211,7 @@ export const SlidesMinimap = ({
196
211
  canReorder,
197
212
  activeCellId,
198
213
  skippedIds,
214
+ noOutputIds,
199
215
  slideTypes,
200
216
  onSlideClick,
201
217
  }: SlidesMinimapProps) => {
@@ -288,6 +304,7 @@ export const SlidesMinimap = ({
288
304
  dimensions={dimensions}
289
305
  isActiveSlide={cell.id === activeCellId}
290
306
  isVisible={visibleIds.has(cell.id)}
307
+ isNoOutput={noOutputIds?.has(cell.id)}
291
308
  slideType={resolveSlideType({
292
309
  cellId: cell.id,
293
310
  slideTypes,
@@ -325,6 +342,7 @@ export const SlidesMinimap = ({
325
342
  isActive={activeId === cell.id}
326
343
  isActiveSlide={cell.id === activeCellId}
327
344
  isVisible={visibleIds.has(cell.id)}
345
+ isNoOutput={noOutputIds?.has(cell.id)}
328
346
  slideType={resolveSlideType({
329
347
  cellId: cell.id,
330
348
  slideTypes,
@@ -347,6 +365,7 @@ export const SlidesMinimap = ({
347
365
  dimensions={dimensions}
348
366
  isOverlay={true}
349
367
  isActiveDragSource={true}
368
+ isNoOutput={noOutputIds?.has(activeCell.id)}
350
369
  />
351
370
  )}
352
371
  </DragOverlay>
@@ -378,6 +397,7 @@ interface SortableSlideThumbnailProps {
378
397
  isActive: boolean;
379
398
  isActiveSlide?: boolean;
380
399
  isVisible?: boolean;
400
+ isNoOutput?: boolean;
381
401
  slideType?: SlideType;
382
402
  onClick?: () => void;
383
403
  }
@@ -389,6 +409,7 @@ const SortableSlideThumbnail = ({
389
409
  isActive,
390
410
  isActiveSlide,
391
411
  isVisible,
412
+ isNoOutput,
392
413
  slideType,
393
414
  onClick,
394
415
  }: SortableSlideThumbnailProps) => {
@@ -405,6 +426,7 @@ const SortableSlideThumbnail = ({
405
426
  isActiveDragSource={isActive}
406
427
  isActiveSlide={isActiveSlide}
407
428
  isVisible={isVisible}
429
+ isNoOutput={isNoOutput}
408
430
  slideType={slideType}
409
431
  onClick={onClick}
410
432
  {...attributes}
@@ -422,6 +444,7 @@ const SlideThumbnailRow = ({
422
444
  isActiveSlide = false,
423
445
  isActiveDragSource = false,
424
446
  isVisible,
447
+ isNoOutput,
425
448
  slideType,
426
449
  onClick,
427
450
  ref,
@@ -462,6 +485,7 @@ const SlideThumbnailRow = ({
462
485
  isActiveSlide={isActiveSlide}
463
486
  isActiveDragSource={isActiveDragSource}
464
487
  isVisible={isVisible}
488
+ isNoOutput={isNoOutput}
465
489
  slideType={slideType}
466
490
  />
467
491
  </button>
@@ -477,13 +501,14 @@ const SlideThumbnailCard = ({
477
501
  isActiveDragSource = false,
478
502
  isOverlay = false,
479
503
  isVisible = false,
504
+ isNoOutput = false,
480
505
  slideType,
481
506
  ref,
482
507
  ...props
483
508
  }: SlideThumbnailCardProps) => {
484
509
  const { width, height, scale } = dimensions;
485
- const visual = getSlideTypeVisual(slideType);
486
- const isSkipped = slideType === "skip";
510
+ const visual = isNoOutput ? NO_OUTPUT_VISUAL : getSlideTypeVisual(slideType);
511
+ const isSkipped = isNoOutput || slideType === "skip";
487
512
 
488
513
  const outerStyle: React.CSSProperties = {
489
514
  width,
@@ -525,16 +550,20 @@ const SlideThumbnailCard = ({
525
550
  height: height / scale,
526
551
  }}
527
552
  >
528
- <Slide cellId={cell.id} status={cell.status} output={cell.output} />
553
+ {isNoOutput ? (
554
+ <MiniCodePreview code={cell.code} />
555
+ ) : (
556
+ <Slide cellId={cell.id} status={cell.status} output={cell.output} />
557
+ )}
529
558
  </div>
530
559
  )}
531
560
  {isSkipped && (
532
561
  <div
533
- className="absolute inset-0 bg-muted/60 pointer-events-none"
562
+ className="absolute inset-0 bg-muted/50 pointer-events-none"
534
563
  aria-hidden={true}
535
564
  />
536
565
  )}
537
- {visual && (
566
+ {visual?.Icon && (
538
567
  <Tooltip
539
568
  content={
540
569
  <span className="text-xs opacity-80">{visual.description}</span>
@@ -550,7 +579,6 @@ const SlideThumbnailCard = ({
550
579
  aria-label={visual.label}
551
580
  >
552
581
  <visual.Icon className="h-3.5 w-3.5" />
553
- {/* <span>{visual.label}</span> */}
554
582
  </span>
555
583
  </Tooltip>
556
584
  )}
@@ -558,6 +586,14 @@ const SlideThumbnailCard = ({
558
586
  );
559
587
  };
560
588
 
589
+ const MiniCodePreview = ({ code }: { code: string }) => {
590
+ return (
591
+ <pre className="my-auto w-full overflow-hidden whitespace-pre-wrap wrap-break-word text-lg">
592
+ {code}
593
+ </pre>
594
+ );
595
+ };
596
+
561
597
  function projectDropTarget(
562
598
  event: DragMoveEvent | DragOverEvent,
563
599
  ): ProjectedDropTarget | null {
@@ -221,26 +221,54 @@ const SubslideView = ({
221
221
  );
222
222
  };
223
223
 
224
+ const ParkedPreviewContent = ({
225
+ cell,
226
+ isNoOutputPreview,
227
+ isEditable,
228
+ codeShown,
229
+ }: {
230
+ cell: RuntimeCell;
231
+ isNoOutputPreview: boolean;
232
+ isEditable: boolean;
233
+ codeShown: boolean;
234
+ }) => {
235
+ if (isNoOutputPreview && isEditable) {
236
+ return <SlideCellView cell={cell} />;
237
+ }
238
+ if (isNoOutputPreview && codeShown) {
239
+ return <SlideCellReadOnlyView cell={cell} />;
240
+ }
241
+ return (
242
+ <CellOutputSlide
243
+ cellId={cell.id}
244
+ status={cell.status}
245
+ output={cell.output}
246
+ />
247
+ );
248
+ };
249
+
224
250
  // There is an upstream react bug in dev mode (https://github.com/facebook/react/issues/34840)
225
251
  // Uncaught SecurityError: Failed to read a named property '$$typeof' from 'Window'
226
252
  // Happens with cells containing iframes / external content
227
253
  const RevealSlidesComponent = ({
228
- cellsWithOutput,
254
+ slideCells,
229
255
  layout,
230
256
  setLayout,
257
+ noOutputIds,
231
258
  activeIndex,
232
259
  onSlideChange,
233
260
  mode,
234
- configWidth = 300, // px
261
+ configWidth, // px
235
262
  isEditable = false,
236
263
  }: {
237
- cellsWithOutput: RuntimeCell[];
264
+ slideCells: RuntimeCell[];
238
265
  layout: SlidesLayout;
239
266
  setLayout: (layout: SlidesLayout) => void;
267
+ noOutputIds: ReadonlySet<CellId>;
240
268
  activeIndex?: number;
241
269
  onSlideChange?: (index: number) => void;
242
270
  mode: AppMode;
243
- configWidth?: number;
271
+ configWidth: number;
244
272
  isEditable?: boolean;
245
273
  }) => {
246
274
  const containerRef = useRef<HTMLDivElement>(null);
@@ -255,42 +283,46 @@ const RevealSlidesComponent = ({
255
283
  );
256
284
 
257
285
  const [showCode, setShowCode] = useState(false);
258
- const codeAvailable = useNotebookCodeAvailable(cellsWithOutput);
286
+ const codeAvailable = useNotebookCodeAvailable(slideCells);
259
287
  const codeToggleEnabled = !isIslands() && codeAvailable;
260
288
  const codeShown = codeToggleEnabled && showCode;
261
289
 
262
- const activeCell =
263
- activeIndex != null ? cellsWithOutput[activeIndex] : undefined;
290
+ const activeCell = activeIndex != null ? slideCells[activeIndex] : undefined;
264
291
  // Fall back to the first cell while the deck settles on an initial slide.
265
292
  // Still `undefined` when the deck is empty (handled below).
266
- const activeConfigCell = activeCell ?? cellsWithOutput.at(0);
293
+ const activeConfigCell = activeCell ?? slideCells.at(0);
267
294
 
268
295
  const composition = useMemo(
269
296
  () =>
270
297
  composeSlides({
271
- cells: cellsWithOutput,
298
+ cells: slideCells,
272
299
  getType: (cell) =>
273
- layout.cells.get(cell.id)?.type ?? DEFAULT_SLIDE_TYPE,
300
+ noOutputIds.has(cell.id)
301
+ ? "skip"
302
+ : (layout.cells.get(cell.id)?.type ?? DEFAULT_SLIDE_TYPE),
274
303
  }),
275
- [cellsWithOutput, layout.cells],
304
+ [slideCells, noOutputIds, layout.cells],
276
305
  );
277
306
 
278
- // Skip cells aren't part of the composed deck. When one is selected in the
279
- // minimap we render a preview over the deck and park reveal on a neighboring
280
- // real slide; keyboard nav while parked is handled below.
281
- const skippedPreviewCell =
282
- activeCell && layout.cells.get(activeCell.id)?.type === "skip"
283
- ? activeCell
284
- : null;
307
+ // Skipped and output-less cells aren't part of the composed deck. When one is
308
+ // selected in the minimap we render a preview over the deck and park reveal on
309
+ // a neighboring real slide; keyboard nav while parked is handled below.
310
+ const activeCellSlideType = activeCell
311
+ ? layout.cells.get(activeCell.id)?.type
312
+ : undefined;
313
+ const isNoOutputPreview =
314
+ activeCell != null && noOutputIds.has(activeCell.id);
315
+ const isParkedPreview = activeCellSlideType === "skip" || isNoOutputPreview;
316
+ const parkedPreviewCell = isParkedPreview ? activeCell : null;
285
317
 
286
318
  const { cellToTarget, targetToCellIndex } = useMemo(
287
319
  () =>
288
320
  buildSlideIndices({
289
321
  composition,
290
- cells: cellsWithOutput,
322
+ cells: slideCells,
291
323
  getId: (c) => c.id,
292
324
  }),
293
- [composition, cellsWithOutput],
325
+ [composition, slideCells],
294
326
  );
295
327
 
296
328
  const deckTransition = layout.deck?.transition ?? DEFAULT_DECK_TRANSITION;
@@ -322,7 +354,7 @@ const RevealSlidesComponent = ({
322
354
  const navigateDeckToActiveCell = useEvent((deck: RevealApi) => {
323
355
  const target = resolveDeckNavigationTarget({
324
356
  activeIndex,
325
- cells: cellsWithOutput,
357
+ cells: slideCells,
326
358
  cellToTarget,
327
359
  getId: (cell) => cell.id,
328
360
  });
@@ -340,7 +372,7 @@ const RevealSlidesComponent = ({
340
372
  return;
341
373
  }
342
374
  navigateDeckToActiveCell(deck);
343
- }, [activeIndex, cellToTarget, cellsWithOutput, navigateDeckToActiveCell]);
375
+ }, [activeIndex, cellToTarget, slideCells, navigateDeckToActiveCell]);
344
376
 
345
377
  // Toggling code (re)mounts a CodeMirror editor on the active slide. Defer
346
378
  // the state update so the button/keypress paints first and the heavier mount
@@ -380,12 +412,12 @@ const RevealSlidesComponent = ({
380
412
  return { h: target.h, v: target.v };
381
413
  }, [activeCell, cellToTarget]);
382
414
 
383
- // Forward the deck's current cell to the parent, except while a skipped
415
+ // Forward the deck's current cell to the parent, except while a parked
384
416
  // preview is parked: every reveal.js event during that window is an echo
385
417
  // of the programmatic park (possibly with transient indices), so ignoring
386
- // them keeps `activeCellId` pinned on the skipped cell.
418
+ // them keeps `activeCellId` pinned on the minimap cell.
387
419
  const reportCurrentCell = useEvent(() => {
388
- if (skippedPreviewCell != null) {
420
+ if (parkedPreviewCell != null) {
389
421
  return;
390
422
  }
391
423
  const deck = deckRef.current;
@@ -401,10 +433,10 @@ const RevealSlidesComponent = ({
401
433
  }
402
434
  });
403
435
 
404
- // While parked on a skipped preview, step through minimap order instead of
436
+ // While parked on a preview, step through minimap order instead of
405
437
  // letting reveal.js advance from the parked slide the user can't see.
406
438
  const handleParkedNavKey = useEvent((event: KeyboardEvent) => {
407
- if (!skippedPreviewCell || activeIndex == null) {
439
+ if (!parkedPreviewCell || activeIndex == null) {
408
440
  return;
409
441
  }
410
442
  if (Events.fromInput(event)) {
@@ -418,7 +450,7 @@ const RevealSlidesComponent = ({
418
450
  event.preventDefault();
419
451
  event.stopPropagation();
420
452
  const nextIndex = activeIndex + direction;
421
- if (nextIndex < 0 || nextIndex >= cellsWithOutput.length) {
453
+ if (nextIndex < 0 || nextIndex >= slideCells.length) {
422
454
  return;
423
455
  }
424
456
  onSlideChange?.(nextIndex);
@@ -431,6 +463,10 @@ const RevealSlidesComponent = ({
431
463
 
432
464
  useEventListener(document, "keydown", handleParkedNavKey, { capture: true });
433
465
 
466
+ const parkedPreviewLabel = isNoOutputPreview
467
+ ? "Hidden as there is no output"
468
+ : "Skipped in presentation";
469
+
434
470
  const slideArea = (
435
471
  <div
436
472
  ref={containerRef}
@@ -439,7 +475,7 @@ const RevealSlidesComponent = ({
439
475
  <div className="group relative" style={{ width, height }}>
440
476
  <Deck
441
477
  deckRef={deckRef}
442
- className="aspect-video w-full overflow-hidden border rounded bg-background mo-slides-theme prose-slides"
478
+ className="aspect-video w-full overflow-hidden border rounded bg-background mo-slides-theme prose-slides focus:outline-none focus-visible:outline-none"
443
479
  config={revealConfig}
444
480
  onReady={handleDeckReady}
445
481
  onSlideChange={handleSlideChange}
@@ -480,21 +516,30 @@ const RevealSlidesComponent = ({
480
516
  );
481
517
  })}
482
518
  </Deck>
483
- {skippedPreviewCell && (
519
+ {parkedPreviewCell && (
484
520
  <div
521
+ key={parkedPreviewCell.id}
485
522
  className="absolute inset-0 z-10 border rounded bg-background flex flex-col overflow-hidden"
486
- aria-label="Skipped in presentation"
523
+ aria-label={parkedPreviewLabel}
487
524
  >
488
525
  <div className="flex items-center gap-1.5 px-3 py-1.5 text-xs text-muted-foreground border-b bg-muted/40">
489
526
  <EyeOffIcon className="h-3.5 w-3.5" />
490
- <span>Skipped in presentation</span>
527
+ <span>{parkedPreviewLabel}</span>
491
528
  </div>
492
529
  <div className="flex-1 overflow-auto flex">
493
- <div className="mo-slide-content" style={{ margin: "auto 20px" }}>
494
- <CellOutputSlide
495
- cellId={skippedPreviewCell.id}
496
- status={skippedPreviewCell.status}
497
- output={skippedPreviewCell.output}
530
+ <div
531
+ className={
532
+ isNoOutputPreview && (isEditable || codeShown)
533
+ ? "mo-slide-content flex flex-col gap-3"
534
+ : "mo-slide-content"
535
+ }
536
+ style={{ margin: "auto 20px" }}
537
+ >
538
+ <ParkedPreviewContent
539
+ cell={parkedPreviewCell}
540
+ isNoOutputPreview={isNoOutputPreview}
541
+ isEditable={isEditable}
542
+ codeShown={codeShown}
498
543
  />
499
544
  </div>
500
545
  </div>
@@ -3,6 +3,7 @@
3
3
  import { useMemo, useRef, useState } from "react";
4
4
  import type { EditorView } from "@codemirror/view";
5
5
  import { useAtomValue } from "jotai";
6
+ import { cellDomProps } from "@/components/editor/common";
6
7
  import { CellEditor } from "@/components/editor/cell/code/cell-editor";
7
8
  import { CellStatusComponent } from "@/components/editor/cell/CellStatus";
8
9
  import { RunButton } from "@/components/editor/cell/RunButton";
@@ -111,7 +112,10 @@ export const SlideCellView = ({ cell }: { cell: RuntimeCell }) => {
111
112
  );
112
113
 
113
114
  const editor = (
114
- <div className={editorWrapperClassName}>
115
+ <div
116
+ className={editorWrapperClassName}
117
+ {...cellDomProps(cell.id, cell.name)}
118
+ >
115
119
  <CellEditor
116
120
  theme={theme}
117
121
  showPlaceholder={false}
@@ -134,7 +138,14 @@ export const SlideCellView = ({ cell }: { cell: RuntimeCell }) => {
134
138
  languageAdapter={languageAdapter}
135
139
  setLanguageAdapter={setLanguageAdapter}
136
140
  showLanguageToggles={false}
141
+ // The reveal.js transforms in slides view break the inline AI
142
+ // tooltip's fixed positioning, so disable it here.
143
+ inlineAiTooltip={false}
137
144
  outputArea={cellOutputPosition}
145
+ // Parent tooltips (completions, hover, signature help) to the Reveal
146
+ // viewport so they stay visible when presenting fullscreen; `#App` sits
147
+ // outside the fullscreened subtree and would never paint.
148
+ tooltipParentSelector=".reveal-viewport"
138
149
  />
139
150
  {toolbar}
140
151
  </div>
@@ -26,15 +26,23 @@ import type {
26
26
  SlidesLayout,
27
27
  SlideType,
28
28
  } from "../editor/renderers/slides-layout/types";
29
- import { useState } from "react";
29
+ import { useAtom } from "jotai";
30
+ import { atomWithStorage } from "jotai/utils";
30
31
  import { Tooltip } from "../ui/tooltip";
31
32
  import { Button } from "../ui/button";
32
33
  import { Kbd } from "../ui/kbd";
33
34
  import type { RuntimeCell } from "@/core/cells/types";
35
+ import { jotaiJsonStorage } from "@/utils/storage/jotai";
34
36
 
35
37
  export const DEFAULT_SLIDE_TYPE: SlideType = "slide";
36
38
  export const DEFAULT_DECK_TRANSITION: DeckTransition = "slide";
37
39
  const COLLAPSED_CONFIG_WIDTH = 36;
40
+ const slideConfigOpenAtom = atomWithStorage<boolean>(
41
+ "marimo:slides:config-open",
42
+ true,
43
+ jotaiJsonStorage,
44
+ { getOnInit: true },
45
+ );
38
46
 
39
47
  export interface SlideTypeOption {
40
48
  value: SlideType;
@@ -322,7 +330,7 @@ export const SlideSidebar = ({
322
330
  setLayout: (layout: SlidesLayout) => void;
323
331
  activeConfigCell?: RuntimeCell;
324
332
  }) => {
325
- const [isConfigOpen, setIsConfigOpen] = useState(false);
333
+ const [isConfigOpen, setIsConfigOpen] = useAtom(slideConfigOpenAtom);
326
334
 
327
335
  return (
328
336
  <aside
@@ -350,7 +358,7 @@ export const SlideSidebar = ({
350
358
  variant="ghost"
351
359
  size="icon"
352
360
  className="h-7 w-7 text-muted-foreground hover:text-foreground"
353
- onClick={() => setIsConfigOpen(!isConfigOpen)}
361
+ onClick={() => setIsConfigOpen((open) => !open)}
354
362
  aria-expanded={isConfigOpen}
355
363
  aria-controls="slide-config-panel"
356
364
  >
@@ -6,6 +6,7 @@ import { useAtomValue } from "jotai";
6
6
  import { CopyIcon, DownloadIcon } from "lucide-react";
7
7
  import type React from "react";
8
8
  import { Constants } from "@/core/constants";
9
+ import { useResolvedMarimoConfig } from "@/core/config/config";
9
10
  import { codeAtom } from "@/core/saving/file-state";
10
11
  import { useFilename } from "@/core/saving/filename";
11
12
  import { isStaticNotebook } from "@/core/static/static-state";
@@ -66,6 +67,9 @@ const StaticBannerDialog = ({ code }: { code: string }) => {
66
67
  filename = filename.slice(lastSlash + 1);
67
68
  }
68
69
 
70
+ const [resolvedConfig] = useResolvedMarimoConfig();
71
+ const molabEnabled = resolvedConfig.sharing?.molab !== false;
72
+
69
73
  const href = window.location.href;
70
74
  const molabLink = createShareableLink({
71
75
  code,
@@ -118,28 +122,30 @@ const StaticBannerDialog = ({ code }: { code: string }) => {
118
122
  </div>
119
123
  )}
120
124
 
121
- <div className="pt-3 border-t flex gap-2 items-center">
122
- <Button
123
- asChild={true}
124
- variant="outline"
125
- size="xs"
126
- className="shrink-0"
127
- >
128
- <a href={molabLink} target="_blank" rel="noopener noreferrer">
129
- <MarimoPlusIcon
130
- size={12}
131
- strokeWidth={1.5}
132
- className="mr-1.5 mt-px text-(--grass-11)"
133
- />
134
- Open in molab
135
- </a>
136
- </Button>
137
- <p className="text-sm text-(--sky-12)">
138
- Run this notebook in{" "}
139
- <span className="font-semibold">molab</span>, marimo's
140
- cloud-hosted notebook platform.
141
- </p>
142
- </div>
125
+ {molabEnabled && (
126
+ <div className="pt-3 border-t flex gap-2 items-center">
127
+ <Button
128
+ asChild={true}
129
+ variant="outline"
130
+ size="xs"
131
+ className="shrink-0"
132
+ >
133
+ <a href={molabLink} target="_blank" rel="noopener noreferrer">
134
+ <MarimoPlusIcon
135
+ size={12}
136
+ strokeWidth={1.5}
137
+ className="mr-1.5 mt-px text-(--grass-11)"
138
+ />
139
+ Open in molab
140
+ </a>
141
+ </Button>
142
+ <p className="text-sm text-(--sky-12)">
143
+ Run this notebook in{" "}
144
+ <span className="font-semibold">molab</span>, marimo's
145
+ cloud-hosted notebook platform.
146
+ </p>
147
+ </div>
148
+ )}
143
149
  </DialogDescription>
144
150
  </DialogHeader>
145
151
  <div className="flex gap-3 pt-2">