@marimo-team/islands 0.25.3-dev267 → 0.25.3-dev268

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 (138) hide show
  1. package/dist/{ConnectedDataExplorerComponent-C4yCN38h.js → ConnectedDataExplorerComponent-eLHPh6uu.js} +8 -8
  2. package/dist/{ErrorBoundary-CewXPyL5.js → ErrorBoundary-CQYAoD7X.js} +4 -4
  3. package/dist/{abnfDiagram-VCTEODGH-o6Ay6Q63.js → abnfDiagram-VCTEODGH-hqd0IBtX.js} +2 -2
  4. package/dist/{architectureDiagram-5GKGNRK7-BhiwwlVg.js → architectureDiagram-5GKGNRK7-BzLAFcc0.js} +1 -1
  5. package/dist/{strings-BCKpLFNU.js → assertNever-DIU3JJbt.js} +14 -14
  6. package/dist/{chat-ui-CbgR5ivy.js → chat-ui-BAnLgpTe.js} +17 -17
  7. package/dist/{chunk-IMKFNOWR-WMSMf4Nj.js → chunk-IMKFNOWR-DOeVvuyE.js} +1 -1
  8. package/dist/{chunk-SHT3W25Y-BRfADrzD.js → chunk-SHT3W25Y-DZoFI8wR.js} +1 -1
  9. package/dist/{chunk-SVP7TREG-CuK5QFre.js → chunk-SVP7TREG-CWu86PSW.js} +1 -1
  10. package/dist/{chunk-TICWLB2K-Bcun_jhJ.js → chunk-TICWLB2K-BrM0GoP3.js} +1 -1
  11. package/dist/{classDiagram-ZZMXUADV-CgWDbUDT.js → classDiagram-ZZMXUADV-DYi4V2bB.js} +2 -2
  12. package/dist/{classDiagram-v2-VYDZK3BY-DYmc9SFU.js → classDiagram-v2-VYDZK3BY-tN7FIk-m.js} +2 -2
  13. package/dist/{code-visibility-BKBl9Rh6.js → code-visibility-DcZ_RwaH.js} +44 -47
  14. package/dist/{common-N3Jd_6m9.js → common-BGeQilvh.js} +4866 -4857
  15. package/dist/{config-CmeG_Pb6.js → config-BsPruWMR.js} +2 -2
  16. package/dist/{createReducer-C_syF8Td.js → createReducer-DdtT_7ji.js} +1 -1
  17. package/dist/{cynefinDiagram-5FMLGOSQ-PPKWipjT.js → cynefinDiagram-5FMLGOSQ-D5nCnDc-.js} +1 -1
  18. package/dist/{dagre-GXQ25YYZ-niRBZh--.js → dagre-GXQ25YYZ-BHkd7f_i.js} +1 -1
  19. package/dist/{data-grid-overlay-editor-Cp59CrqZ.js → data-grid-overlay-editor-Dc4lW4A3.js} +1 -1
  20. package/dist/{diagram-S7CK7UJ4-DPiP9rot.js → diagram-S7CK7UJ4-D1ALppT2.js} +1 -1
  21. package/dist/{diagram-UQ7AKVKN-B0WZuaxV.js → diagram-UQ7AKVKN-DLv0b83L.js} +1 -1
  22. package/dist/{diagram-VX7I27RA-Rn42WBuN.js → diagram-VX7I27RA-7XJ7J6qg.js} +1 -1
  23. package/dist/{diagram-Z3DM3KII-BB93--ry.js → diagram-Z3DM3KII-7M7zbHp_.js} +1 -1
  24. package/dist/{dialog-BtprQCUw.js → dialog-DzCj1qQv.js} +1 -1
  25. package/dist/{dropdown-menu-BpImEfn3.js → dropdown-menu-CdXE15an.js} +3 -3
  26. package/dist/{ebnfDiagram-PWID7BFC-V4t5h6mZ.js → ebnfDiagram-PWID7BFC-DHH1qlmV.js} +2 -2
  27. package/dist/{erDiagram-RLTQ6QDP-C1MsAKXd.js → erDiagram-RLTQ6QDP-fI_0dGef.js} +1 -1
  28. package/dist/{error-entries-CJGdD2ky.js → error-entries-CxN-Nq3V.js} +519 -458
  29. package/dist/{feedback-button-Qc1f9iPq.js → feedback-button-BqaeQcvX.js} +22 -22
  30. package/dist/{flowDiagram-HODETNUW-DHjeJP96.js → flowDiagram-HODETNUW-Brg9MA6i.js} +2 -2
  31. package/dist/{formats-gGospKWJ.js → formats-DbSvAPY-.js} +1 -1
  32. package/dist/{glide-data-editor-hCq3K0sd.js → glide-data-editor-CiijPXA7.js} +11 -11
  33. package/dist/{glide-utils-DC3sk6EX.js → glide-utils-BSw3SSHF.js} +1 -1
  34. package/dist/{globals-B2_uMYDY.js → globals-CpmSWKI_.js} +2 -2
  35. package/dist/{html-to-image-S7spRQCi.js → html-to-image-8sywF4my.js} +5120 -5117
  36. package/dist/{infoDiagram-27XIBGKW-Dovv6iKx.js → infoDiagram-27XIBGKW-E9e403YT.js} +1 -1
  37. package/dist/{ishikawaDiagram-5VMMS53U-D2nyygsc.js → ishikawaDiagram-5VMMS53U-C-RlYDRo.js} +1 -1
  38. package/dist/{kanban-definition-UXKFOSKX-KyVx0uTY.js → kanban-definition-UXKFOSKX-CLFl_uqK.js} +1 -1
  39. package/dist/main.js +5972 -6003
  40. package/dist/{mermaid-B-jKSwGt.js → mermaid-rd09e1JL.js} +34 -34
  41. package/dist/{mindmap-definition-YA3MSWOX-Bz9tKQFl.js → mindmap-definition-YA3MSWOX-BQ8TWtyB.js} +1 -1
  42. package/dist/{number-overlay-editor-CfNYvbdz.js → number-overlay-editor-Ft4NiAEN.js} +1 -1
  43. package/dist/{pegDiagram-XKGWAZYB-C_qpq6Tv.js → pegDiagram-XKGWAZYB-CN8UHFPi.js} +2 -2
  44. package/dist/{pieDiagram-E7YTZNPT-Bp9ucgI4.js → pieDiagram-E7YTZNPT-DGuJ0EBd.js} +1 -1
  45. package/dist/{process-output-f34bkXTm.js → process-output-Dku5POvm.js} +3 -3
  46. package/dist/{railroadDiagram-O6MQD6OU-tq-VEH6j.js → railroadDiagram-O6MQD6OU-BPDA8wy8.js} +2 -2
  47. package/dist/{requirementDiagram-BXWQKSXE-DW-vHadb.js → requirementDiagram-BXWQKSXE-ChzLGbGW.js} +1 -1
  48. package/dist/{reveal-component-CUxv-kkP.js → reveal-component-D0cfwbbl.js} +1094 -1094
  49. package/dist/{select-C33FVPPN.js → select-OxgozOC8.js} +1 -1
  50. package/dist/{sequenceDiagram-WJ2MYXX4-Dv9U9qRP.js → sequenceDiagram-WJ2MYXX4-DFX-bE6O.js} +1 -1
  51. package/dist/{spec-DH1lmF7Q.js → spec-oJn0RhpQ.js} +2 -2
  52. package/dist/{stateDiagram-D77RDMKH-DTmPQ0EB.js → stateDiagram-D77RDMKH-BR7cI3wK.js} +2 -2
  53. package/dist/{stateDiagram-v2-MP3YSRHH-CkCif0Iw.js → stateDiagram-v2-MP3YSRHH-oGqB5UDI.js} +2 -2
  54. package/dist/{static-state-BldGd99T.js → static-state-CuV6Pc3a.js} +1 -1
  55. package/dist/{swimlanes-42K2YHIH-21kCq6l8.js → swimlanes-42K2YHIH-DwSZl1yC.js} +1 -1
  56. package/dist/{swimlanesDiagram-VR7AAH4N-BpRJP6KU.js → swimlanesDiagram-VR7AAH4N-BHlcd-Ri.js} +2 -2
  57. package/dist/{timeline-definition-24CTP7MA-Ckgh4lS4.js → timeline-definition-24CTP7MA-DMmP-G2O.js} +1 -1
  58. package/dist/{toast-DpczUryP.js → toast-DbBHmxmB.js} +1 -1
  59. package/dist/{useAsyncData-DsZMvNXn.js → useAsyncData-BYIAoMqn.js} +1 -1
  60. package/dist/{useDateFormatter-Dk_xFci4.js → useDateFormatter-ButWkdcK.js} +1 -1
  61. package/dist/{useDeepCompareMemoize-KMBBux8C.js → useDeepCompareMemoize-Bz0c5GN6.js} +1 -1
  62. package/dist/{useTheme-BZ2GGLgm.js → useTheme-DPLobSFs.js} +7 -7
  63. package/dist/{utils-DXkj82sK.js → utils-EBklsC5R.js} +31 -31
  64. package/dist/{vega-component-CF9kI6C4.js → vega-component-rxIxAAh9.js} +6 -6
  65. package/dist/{vennDiagram-4TSXK5OY-CDei2Bt3.js → vennDiagram-4TSXK5OY-Db8GtH1G.js} +1 -1
  66. package/dist/{wardleyDiagram-VM6X3IG4-CXavT601.js → wardleyDiagram-VM6X3IG4-DmIiC3FU.js} +1 -1
  67. package/dist/{xychartDiagram-S5SC5T6Z-CvJhcZri.js → xychartDiagram-S5SC5T6Z-DS9OPrNq.js} +1 -1
  68. package/package.json +1 -1
  69. package/src/__mocks__/executions.ts +195 -0
  70. package/src/__mocks__/notebook.ts +0 -1
  71. package/src/__tests__/CellStatus.test.tsx +19 -20
  72. package/src/components/dependency-graph/minimap-content.tsx +3 -2
  73. package/src/components/editor/__tests__/data-attributes.test.tsx +0 -7
  74. package/src/components/editor/__tests__/output-persistence.test.tsx +2 -2
  75. package/src/components/editor/actions/__tests__/useCellActionButton.test.tsx +1 -0
  76. package/src/components/editor/actions/useCellActionButton.tsx +7 -3
  77. package/src/components/editor/alerts/stdin-blocking-alert.tsx +3 -1
  78. package/src/components/editor/cell/CellStatus.tsx +23 -19
  79. package/src/components/editor/cell/RunButton.tsx +16 -6
  80. package/src/components/editor/cell/__tests__/useRunCells.test.ts +45 -0
  81. package/src/components/editor/cell/cell-context-menu.tsx +4 -1
  82. package/src/components/editor/cell/code/cell-editor.tsx +7 -5
  83. package/src/components/editor/cell/collapse.tsx +4 -0
  84. package/src/components/editor/cell/useRunCells.ts +18 -3
  85. package/src/components/editor/chrome/wrapper/sidebar.tsx +2 -4
  86. package/src/components/editor/header/status.tsx +4 -2
  87. package/src/components/editor/navigation/navigation.ts +3 -2
  88. package/src/components/editor/notebook-cell.tsx +82 -83
  89. package/src/components/editor/renderers/grid-layout/__tests__/grid-layout.test.tsx +75 -0
  90. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +22 -16
  91. package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +0 -1
  92. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +12 -33
  93. package/src/components/editor/stdin-blocking-alert.tsx +3 -1
  94. package/src/components/scratchpad/scratchpad.tsx +4 -1
  95. package/src/components/slides/minimap.tsx +4 -2
  96. package/src/components/slides/reveal-component.tsx +14 -10
  97. package/src/components/slides/slide-cell-view.tsx +28 -24
  98. package/src/components/slides/slide.tsx +3 -4
  99. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +0 -389
  100. package/src/core/cells/__tests__/cell.test.ts +49 -154
  101. package/src/core/cells/__tests__/cells.test.ts +18 -127
  102. package/src/core/cells/__tests__/pending-delete-service.test.tsx +11 -20
  103. package/src/core/cells/__tests__/utils.test.ts +75 -247
  104. package/src/core/cells/actions.ts +5 -2
  105. package/src/core/cells/cell.ts +31 -133
  106. package/src/core/cells/cells.ts +14 -29
  107. package/src/core/cells/document.ts +3 -3
  108. package/src/core/cells/focus.ts +27 -6
  109. package/src/core/cells/layout.ts +1 -1
  110. package/src/core/cells/pending-delete-service.ts +5 -7
  111. package/src/core/cells/types.ts +13 -36
  112. package/src/core/cells/utils.ts +37 -68
  113. package/src/core/edit-app.tsx +2 -1
  114. package/src/core/executions/__tests__/atoms.test.ts +99 -0
  115. package/src/core/executions/__tests__/select.test.ts +268 -0
  116. package/src/core/executions/atoms.ts +206 -0
  117. package/src/core/executions/hooks.ts +31 -0
  118. package/src/core/executions/index.ts +31 -0
  119. package/src/core/executions/select.ts +175 -0
  120. package/src/core/export/__tests__/hooks.test.ts +0 -5
  121. package/src/core/islands/bootstrap.ts +4 -1
  122. package/src/core/islands/components/output-wrapper.tsx +5 -5
  123. package/src/core/kernel/handlers.ts +2 -11
  124. package/src/core/kernel/state.ts +0 -15
  125. package/src/core/lifecycle/render-policy.ts +12 -26
  126. package/src/core/network/__tests__/requests-lazy.test.ts +5 -5
  127. package/src/core/network/requests-lazy.ts +3 -3
  128. package/src/core/notebook/operations.ts +1 -1
  129. package/src/core/run-app.tsx +2 -1
  130. package/src/core/runtime/__tests__/adapter.test.ts +3 -3
  131. package/src/core/runtime/adapter.ts +4 -4
  132. package/src/core/session/useNotebookSession.tsx +3 -3
  133. package/src/core/wasm/bridge.ts +1 -1
  134. package/src/plugins/core/registerReactComponent.tsx +4 -13
  135. package/src/stories/cell-status.stories.tsx +17 -10
  136. package/src/stories/cell.stories.tsx +0 -31
  137. package/src/stories/layout/vertical/one-column.stories.tsx +0 -9
  138. package/src/utils/time.ts +10 -0
@@ -37,10 +37,14 @@ import { isErrorMime } from "@/core/mime";
37
37
  import type { AppMode } from "@/core/mode";
38
38
  import { connectionAtom } from "@/core/network/connection";
39
39
  import { useRequestClient } from "@/core/network/requests";
40
- import type { CellConfig, RuntimeState } from "@/core/network/types";
40
+ import {
41
+ type CellExecutionView,
42
+ type CellRunStatus,
43
+ useCellExecution,
44
+ } from "@/core/executions";
45
+ import type { CellConfig } from "@/core/network/types";
41
46
  import { useResizeObserver } from "@/hooks/useResizeObserver";
42
47
  import { cn } from "@/utils/cn";
43
- import type { Milliseconds, Seconds } from "@/utils/time";
44
48
  import {
45
49
  useCellData,
46
50
  useCellHandle,
@@ -315,6 +319,10 @@ const ReadonlyCellComponent = forwardRef(
315
319
  const { cellId } = props;
316
320
  const cellData = useCellData(cellId);
317
321
  const cellRuntime = useCellRuntime(cellId);
322
+ const execution = useCellExecution(cellId);
323
+ const errored = execution.outcome === "error";
324
+ const interrupted = execution.outcome === "interrupted";
325
+ const stopped = execution.outcome === "skipped";
318
326
 
319
327
  const className = clsx("marimo-cell", "hover-actions-parent z-10", {
320
328
  published: true,
@@ -322,9 +330,9 @@ const ReadonlyCellComponent = forwardRef(
322
330
 
323
331
  // Hide the output if it's an error or stopped.
324
332
  const hidden = shouldHidePublishedCell({
325
- errored: cellRuntime.errored,
326
- interrupted: cellRuntime.interrupted,
327
- stopped: cellRuntime.stopped,
333
+ errored,
334
+ interrupted,
335
+ stopped,
328
336
  output: cellRuntime.output,
329
337
  showErrorTracebacks: false,
330
338
  });
@@ -346,8 +354,12 @@ const ReadonlyCellComponent = forwardRef(
346
354
  className={CSSClasses.outputArea}
347
355
  cellId={cellId}
348
356
  output={cellRuntime.output}
349
- stale={outputIsStale(cellRuntime, cellData.edited)}
350
- loading={outputIsLoading(cellRuntime.status)}
357
+ stale={outputIsStale(
358
+ execution,
359
+ cellRuntime.staleInputs,
360
+ cellData.edited,
361
+ )}
362
+ loading={outputIsLoading(execution.status)}
351
363
  />
352
364
  </div>
353
365
  );
@@ -374,6 +386,10 @@ const EditableCellComponent = ({
374
386
  const cellRef = useRef<HTMLDivElement>(null);
375
387
  const cellData = useCellData(cellId);
376
388
  const cellRuntime = useCellRuntime(cellId);
389
+ const execution = useCellExecution(cellId);
390
+ const errored = execution.outcome === "error";
391
+ const interrupted = execution.outcome === "interrupted";
392
+ const stopped = execution.outcome === "skipped";
377
393
  const cellActionDropdownRef = useRef<CellActionsDropdownHandle>(null);
378
394
  // DOM node where the editorView will be mounted
379
395
  const editorViewParentRef = useRef<HTMLDivElement>(null);
@@ -389,23 +405,17 @@ const EditableCellComponent = ({
389
405
 
390
406
  const [languageAdapter, setLanguageAdapter] = useState<LanguageAdapterType>();
391
407
 
392
- const uninstantiated = isUninstantiated({
393
- executionTime: cellRuntime.runElapsedTimeMs ?? cellData.lastExecutionTime,
394
- status: cellRuntime.status,
395
- errored: cellRuntime.errored,
396
- interrupted: cellRuntime.interrupted,
397
- stopped: cellRuntime.stopped,
398
- });
408
+ const uninstantiated = isUninstantiated(execution);
399
409
 
400
410
  const needsRun = cellNeedsRun({
401
411
  edited: cellData.edited,
402
- interrupted: cellRuntime.interrupted,
412
+ interrupted,
403
413
  staleInputs: cellRuntime.staleInputs,
404
414
  disabled: cellData.config.disabled,
405
- status: cellRuntime.status,
415
+ blocked: execution.blocked,
406
416
  });
407
417
 
408
- const loading = outputIsLoading(cellRuntime.status);
418
+ const loading = outputIsLoading(execution.status);
409
419
 
410
420
  const isPresenting = mode === "present";
411
421
 
@@ -414,9 +424,9 @@ const EditableCellComponent = ({
414
424
  const presentHidden =
415
425
  isPresenting &&
416
426
  shouldHidePublishedCell({
417
- errored: cellRuntime.errored,
418
- interrupted: cellRuntime.interrupted,
419
- stopped: cellRuntime.stopped,
427
+ errored,
428
+ interrupted,
429
+ stopped,
420
430
  output: cellRuntime.output,
421
431
  showErrorTracebacks: userConfig.runtime.show_tracebacks ?? false,
422
432
  });
@@ -432,10 +442,10 @@ const EditableCellComponent = ({
432
442
  // console output is cleared immediately on run, so check for queued instead
433
443
  // of loading to determine staleness
434
444
  const consoleOutputStale =
435
- (cellRuntime.status === "queued" ||
445
+ (execution.status === "queued" ||
436
446
  cellData.edited ||
437
447
  cellRuntime.staleInputs) &&
438
- !cellRuntime.interrupted;
448
+ !interrupted;
439
449
 
440
450
  // This getter is called during render, so useEvent cannot be used here.
441
451
  // oxlint-disable-next-line react/preserve-manual-memoization
@@ -472,7 +482,8 @@ const EditableCellComponent = ({
472
482
  // While presenting, pending edits do not dim the output as stale,
473
483
  // matching the read view.
474
484
  const isStaleCell = outputIsStale(
475
- cellRuntime,
485
+ execution,
486
+ cellRuntime.staleInputs,
476
487
  cellData.edited && !isPresenting,
477
488
  );
478
489
  const hasConsoleOutput = cellRuntime.consoleOutputs.length > 0;
@@ -568,7 +579,7 @@ const EditableCellComponent = ({
568
579
  cellId={cellId}
569
580
  output={cellRuntime.output}
570
581
  stale={isStaleCell}
571
- loading={outputIsLoading(cellRuntime.status)}
582
+ loading={outputIsLoading(execution.status)}
572
583
  />
573
584
  </div>
574
585
  );
@@ -579,18 +590,18 @@ const EditableCellComponent = ({
579
590
  isPresenting
580
591
  ? // Match the published styling of the read view
581
592
  publishedCellClasses({
582
- errored: cellRuntime.errored,
583
- stopped: cellRuntime.stopped,
593
+ errored,
594
+ stopped,
584
595
  })
585
596
  : {
586
597
  "z-10": true,
587
598
  interactive: true,
588
599
  ...cellStatusClasses({
589
600
  needsRun,
590
- errored: cellRuntime.errored,
591
- stopped: cellRuntime.stopped,
601
+ errored,
602
+ stopped,
592
603
  disabled: cellData.config.disabled,
593
- status: cellRuntime.status,
604
+ blocked: execution.blocked,
594
605
  }),
595
606
  borderless:
596
607
  isMarkdownCodeHidden &&
@@ -617,7 +628,7 @@ const EditableCellComponent = ({
617
628
  if (cellData.config.disabled) {
618
629
  return "This cell is disabled";
619
630
  }
620
- if (cellRuntime.status === "disabled-transitively") {
631
+ if (execution.blocked) {
621
632
  return "This cell has a disabled ancestor";
622
633
  }
623
634
  return undefined;
@@ -635,7 +646,7 @@ const EditableCellComponent = ({
635
646
  <SortableCell
636
647
  tabIndex={-1}
637
648
  ref={cellRef}
638
- data-status={cellRuntime.status}
649
+ data-status={execution.status}
639
650
  onBlur={closeCompletionHandler}
640
651
  onKeyDown={resumeCompletionHandler}
641
652
  cellId={cellId}
@@ -675,7 +686,8 @@ const EditableCellComponent = ({
675
686
  <div className="absolute right-2 -top-4 z-10">
676
687
  <CellToolbar
677
688
  edited={cellData.edited}
678
- status={cellRuntime.status}
689
+ status={execution.status}
690
+ blocked={execution.blocked}
679
691
  cellConfig={cellData.config}
680
692
  needsRun={needsRun}
681
693
  hasOutput={hasOutput}
@@ -693,7 +705,7 @@ const EditableCellComponent = ({
693
705
  id={cellId}
694
706
  code={cellData.code}
695
707
  config={cellData.config}
696
- status={cellRuntime.status}
708
+ status={execution.status}
697
709
  runCell={runCell}
698
710
  setEditorView={setEditorView}
699
711
  userConfig={userConfig}
@@ -714,21 +726,17 @@ const EditableCellComponent = ({
714
726
  isMarkdownCodeHidden && cellOutput === "below" && "top-14",
715
727
  )}
716
728
  edited={cellData.edited}
717
- status={cellRuntime.status}
729
+ execution={execution}
718
730
  isCellStatusInline={isCellStatusInline}
719
731
  uninstantiated={uninstantiated}
720
732
  disabled={cellData.config.disabled}
721
- runElapsedTimeMs={cellRuntime.runElapsedTimeMs}
722
- runStartTimestamp={cellRuntime.runStartTimestamp}
723
- lastRunStartTimestamp={cellRuntime.lastRunStartTimestamp}
724
733
  staleInputs={cellRuntime.staleInputs}
725
- interrupted={cellRuntime.interrupted}
726
734
  />
727
735
  )}
728
736
  <div className="shoulder-bottom hover-action">
729
737
  {canDelete && isCellCodeShown && (
730
738
  <DeleteButton
731
- status={cellRuntime.status}
739
+ status={execution.status}
732
740
  connectionState={connection.state}
733
741
  onClick={() => {
734
742
  if (
@@ -745,7 +753,7 @@ const EditableCellComponent = ({
745
753
  {!isPresenting && (
746
754
  <SqlValidationErrorBanner
747
755
  cellId={cellId}
748
- hide={cellRuntime.errored && !isStaleCell}
756
+ hide={errored && !isStaleCell}
749
757
  />
750
758
  )}
751
759
  {cellOutput === "below" && (outputArea || emptyMarkdownPlaceholder)}
@@ -811,7 +819,7 @@ const EditableCellComponent = ({
811
819
  <ConsoleOutput
812
820
  consoleOutputs={cellRuntime.consoleOutputs}
813
821
  stale={consoleOutputStale}
814
- interrupted={cellRuntime.interrupted}
822
+ interrupted={interrupted}
815
823
  // Empty name if serialization triggered
816
824
  cellName={cellRuntime.serialization ? "_" : cellData.name}
817
825
  onRefactorWithAI={handleRefactorWithAI}
@@ -853,41 +861,33 @@ const CellRightSideActions = memo(
853
861
  className?: string;
854
862
  disabled: boolean | undefined;
855
863
  edited: boolean;
856
- interrupted: boolean;
864
+ execution: CellExecutionView;
857
865
  isCellStatusInline: boolean;
858
- lastRunStartTimestamp: Seconds | null;
859
- runElapsedTimeMs: Milliseconds | null;
860
- runStartTimestamp: Seconds | null;
861
866
  staleInputs: boolean;
862
- status: RuntimeState;
863
867
  uninstantiated: boolean;
864
868
  }) => {
865
869
  const {
866
870
  className,
867
871
  disabled = false,
868
872
  edited,
869
- interrupted,
873
+ execution,
870
874
  isCellStatusInline,
871
- lastRunStartTimestamp,
872
- runElapsedTimeMs,
873
- runStartTimestamp,
874
875
  staleInputs,
875
- status,
876
876
  uninstantiated,
877
877
  } = props;
878
878
 
879
879
  const cellStatusComponent = (
880
880
  <CellStatusComponent
881
- status={status}
881
+ status={execution.status}
882
882
  staleInputs={staleInputs}
883
- interrupted={interrupted}
883
+ interrupted={execution.outcome === "interrupted"}
884
884
  editing={true}
885
885
  edited={edited}
886
886
  disabled={disabled}
887
- elapsedTime={runElapsedTimeMs}
888
- runStartTimestamp={runStartTimestamp}
887
+ elapsedTime={execution.duration}
888
+ startedAt={execution.startedAt}
889
+ blocked={execution.blocked}
889
890
  uninstantiated={uninstantiated}
890
- lastRunStartTimestamp={lastRunStartTimestamp}
891
891
  />
892
892
  );
893
893
 
@@ -960,7 +960,8 @@ CellLeftSideActions.displayName = "CellLeftSideActions";
960
960
 
961
961
  interface CellToolbarProps {
962
962
  edited: boolean;
963
- status: RuntimeState;
963
+ status: CellRunStatus;
964
+ blocked: boolean;
964
965
  cellConfig: CellConfig;
965
966
  needsRun: boolean;
966
967
  hasOutput: boolean;
@@ -977,6 +978,7 @@ const CellToolbar = memo(
977
978
  ({
978
979
  edited,
979
980
  status,
981
+ blocked,
980
982
  cellConfig,
981
983
  needsRun,
982
984
  hasOutput,
@@ -1002,6 +1004,7 @@ const CellToolbar = memo(
1002
1004
  onClick={onRun}
1003
1005
  connectionState={connection.state}
1004
1006
  status={status}
1007
+ blocked={blocked}
1005
1008
  config={cellConfig}
1006
1009
  needsRun={needsRun}
1007
1010
  />
@@ -1011,6 +1014,7 @@ const CellToolbar = memo(
1011
1014
  ref={cellActionDropdownRef}
1012
1015
  cellId={cellId}
1013
1016
  status={status}
1017
+ blocked={blocked}
1014
1018
  getEditorView={getEditorView}
1015
1019
  name={name}
1016
1020
  config={cellConfig}
@@ -1054,6 +1058,10 @@ const SetupCellComponent = ({
1054
1058
  const cellRef = useRef<HTMLDivElement>(null);
1055
1059
  const cellData = useCellData(cellId);
1056
1060
  const cellRuntime = useCellRuntime(cellId);
1061
+ const execution = useCellExecution(cellId);
1062
+ const errored = execution.outcome === "error";
1063
+ const interrupted = execution.outcome === "interrupted";
1064
+ const stopped = execution.outcome === "skipped";
1057
1065
  const cellActionDropdownRef = useRef<CellActionsDropdownHandle>(null);
1058
1066
  // DOM node where the editorView will be mounted
1059
1067
  const editorViewParentRef = useRef<HTMLDivElement>(null);
@@ -1065,31 +1073,25 @@ const SetupCellComponent = ({
1065
1073
  const setAiCompletionCell = useSetAtom(aiCompletionCellAtom);
1066
1074
  const runCell = useRunCell(cellId);
1067
1075
 
1068
- const uninstantiated = isUninstantiated({
1069
- executionTime: cellRuntime.runElapsedTimeMs ?? cellData.lastExecutionTime,
1070
- status: cellRuntime.status,
1071
- errored: cellRuntime.errored,
1072
- interrupted: cellRuntime.interrupted,
1073
- stopped: cellRuntime.stopped,
1074
- });
1076
+ const uninstantiated = isUninstantiated(execution);
1075
1077
 
1076
1078
  const needsRun = cellNeedsRun({
1077
1079
  edited: cellData.edited,
1078
- interrupted: cellRuntime.interrupted,
1080
+ interrupted,
1079
1081
  staleInputs: cellRuntime.staleInputs,
1080
1082
  disabled: cellData.config.disabled,
1081
- status: cellRuntime.status,
1083
+ blocked: execution.blocked,
1082
1084
  });
1083
1085
  const loading =
1084
- cellRuntime.status === "running" || cellRuntime.status === "queued";
1086
+ execution.status === "running" || execution.status === "queued";
1085
1087
 
1086
1088
  // console output is cleared immediately on run, so check for queued instead
1087
1089
  // of loading to determine staleness
1088
1090
  const consoleOutputStale =
1089
- (cellRuntime.status === "queued" ||
1091
+ (execution.status === "queued" ||
1090
1092
  cellData.edited ||
1091
1093
  cellRuntime.staleInputs) &&
1092
- !cellRuntime.interrupted;
1094
+ !interrupted;
1093
1095
 
1094
1096
  // This getter is called during render, so useEvent cannot be used here.
1095
1097
  // oxlint-disable-next-line react/preserve-manual-memoization
@@ -1123,10 +1125,10 @@ const SetupCellComponent = ({
1123
1125
  interactive: true,
1124
1126
  ...cellStatusClasses({
1125
1127
  needsRun,
1126
- errored: cellRuntime.errored,
1127
- stopped: cellRuntime.stopped,
1128
+ errored,
1129
+ stopped,
1128
1130
  disabled: cellData.config.disabled,
1129
- status: cellRuntime.status,
1131
+ blocked: execution.blocked,
1130
1132
  }),
1131
1133
  });
1132
1134
 
@@ -1147,7 +1149,7 @@ const SetupCellComponent = ({
1147
1149
  if (cellData.config.disabled) {
1148
1150
  return "This cell is disabled";
1149
1151
  }
1150
- if (cellRuntime.status === "disabled-transitively") {
1152
+ if (execution.blocked) {
1151
1153
  return "This cell has a disabled ancestor";
1152
1154
  }
1153
1155
  return undefined;
@@ -1174,7 +1176,7 @@ const SetupCellComponent = ({
1174
1176
  mounted) while presenting. */}
1175
1177
  <div hidden={isPresenting}>
1176
1178
  <div
1177
- data-status={cellRuntime.status}
1179
+ data-status={execution.status}
1178
1180
  ref={cellRef}
1179
1181
  {...mergeProps(navigationProps, {
1180
1182
  className: cn(
@@ -1198,7 +1200,8 @@ const SetupCellComponent = ({
1198
1200
  <div className="absolute right-2 -top-4 z-10">
1199
1201
  <CellToolbar
1200
1202
  edited={cellData.edited}
1201
- status={cellRuntime.status}
1203
+ status={execution.status}
1204
+ blocked={execution.blocked}
1202
1205
  cellConfig={cellData.config}
1203
1206
  needsRun={needsRun}
1204
1207
  hasOutput={hasOutput}
@@ -1217,7 +1220,7 @@ const SetupCellComponent = ({
1217
1220
  id={cellId}
1218
1221
  code={cellData.code}
1219
1222
  config={cellData.config}
1220
- status={cellRuntime.status}
1223
+ status={execution.status}
1221
1224
  runCell={runCell}
1222
1225
  setEditorView={setEditorView}
1223
1226
  userConfig={userConfig}
@@ -1235,22 +1238,18 @@ const SetupCellComponent = ({
1235
1238
  {!isPresenting && (
1236
1239
  <CellRightSideActions
1237
1240
  edited={cellData.edited}
1238
- status={cellRuntime.status}
1241
+ execution={execution}
1239
1242
  isCellStatusInline={false}
1240
1243
  uninstantiated={uninstantiated}
1241
1244
  disabled={cellData.config.disabled}
1242
- runElapsedTimeMs={cellRuntime.runElapsedTimeMs}
1243
- runStartTimestamp={cellRuntime.runStartTimestamp}
1244
- lastRunStartTimestamp={cellRuntime.lastRunStartTimestamp}
1245
1245
  staleInputs={cellRuntime.staleInputs}
1246
- interrupted={cellRuntime.interrupted}
1247
1246
  />
1248
1247
  )}
1249
1248
  <div className="shoulder-bottom hover-action">
1250
1249
  {canDelete && (
1251
1250
  <DeleteButton
1252
1251
  connectionState={connection.state}
1253
- status={cellRuntime.status}
1252
+ status={execution.status}
1254
1253
  onClick={() => {
1255
1254
  if (
1256
1255
  !loading &&
@@ -1298,7 +1297,7 @@ const SetupCellComponent = ({
1298
1297
  <ConsoleOutput
1299
1298
  consoleOutputs={cellRuntime.consoleOutputs}
1300
1299
  stale={consoleOutputStale}
1301
- interrupted={cellRuntime.interrupted}
1300
+ interrupted={interrupted}
1302
1301
  // Don't show name
1303
1302
  cellName={"_"}
1304
1303
  onRefactorWithAI={handleRefactorWithAI}
@@ -0,0 +1,75 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { render, screen } from "@testing-library/react";
4
+ import { createStore, Provider } from "jotai";
5
+ import { describe, expect, it, vi } from "vitest";
6
+ import { MockExecutions } from "@/__mocks__/executions";
7
+ import { MockNotebook } from "@/__mocks__/notebook";
8
+ import { cellId } from "@/__tests__/branded";
9
+ import { parseAppConfig } from "@/core/config/config-schema";
10
+ import { GridLayoutRenderer } from "../grid-layout";
11
+ import type { GridLayout } from "../types";
12
+
13
+ vi.mock("@/components/editor/Output", () => ({
14
+ OutputArea: ({ cellId }: { cellId: string }) => (
15
+ <div data-testid={`output-${cellId}`} />
16
+ ),
17
+ }));
18
+
19
+ const ERRORED = cellId("errored");
20
+
21
+ function renderErroredCell(cells: GridLayout["cells"]) {
22
+ const store = createStore();
23
+ MockExecutions.seed(store, [MockExecutions.errored(ERRORED)]);
24
+ const notebook = MockNotebook.notebookState({
25
+ cellData: { [ERRORED]: { code: "raise ValueError('boom')" } },
26
+ cellRuntime: {
27
+ [ERRORED]: {
28
+ output: {
29
+ channel: "marimo-error",
30
+ mimetype: "application/vnd.marimo+error",
31
+ data: [MockNotebook.errors.exception("boom")],
32
+ timestamp: 1,
33
+ },
34
+ },
35
+ },
36
+ });
37
+ render(
38
+ <Provider store={store}>
39
+ <GridLayoutRenderer
40
+ appConfig={parseAppConfig({})}
41
+ mode="edit"
42
+ layout={{
43
+ columns: 12,
44
+ rowHeight: 20,
45
+ cells,
46
+ scrollableCells: new Set(),
47
+ cellSide: new Map(),
48
+ }}
49
+ setLayout={vi.fn()}
50
+ cells={[
51
+ { ...notebook.cellData[ERRORED], ...notebook.cellRuntime[ERRORED] },
52
+ ]}
53
+ />
54
+ </Provider>,
55
+ );
56
+ return screen.getAllByTestId(`output-${ERRORED}`);
57
+ }
58
+
59
+ describe("GridLayoutRenderer", () => {
60
+ it("hides an errored cell placed in the grid", () => {
61
+ const [placed] = renderErroredCell([
62
+ { i: ERRORED, x: 0, y: 0, w: 4, h: 4 },
63
+ ]);
64
+ expect(placed.closest(".invisible")).not.toBeNull();
65
+ });
66
+
67
+ it("keeps an errored cell visible in the Outputs tray", () => {
68
+ const outputs = renderErroredCell([]);
69
+ expect(screen.getByText("Outputs")).toBeTruthy();
70
+ expect(outputs.length).toBeGreaterThan(0);
71
+ for (const output of outputs) {
72
+ expect(output.closest(".invisible")).toBeNull();
73
+ }
74
+ });
75
+ });
@@ -37,6 +37,7 @@ import { NumberField } from "@/components/ui/number-field";
37
37
  import { Switch } from "@/components/ui/switch";
38
38
  import { outputIsLoading, outputIsStale } from "@/core/cells/cell";
39
39
  import type { CellId } from "@/core/cells/ids";
40
+ import { useCellExecution } from "@/core/executions";
40
41
  import type { AppMode } from "@/core/mode";
41
42
  import { useIsDragging } from "@/hooks/useIsDragging";
42
43
  import { cn } from "@/utils/cn";
@@ -227,11 +228,10 @@ export const GridLayoutRenderer: React.FC<Props> = ({
227
228
  mode={mode}
228
229
  cellId={cell.id}
229
230
  output={cell.output}
230
- status={cell.status}
231
- stale={outputIsStale(cell, false)}
231
+ staleInputs={cell.staleInputs}
232
+ hideWithoutValue={true}
232
233
  isScrollable={isScrollable}
233
234
  side={side}
234
- hidden={cell.errored || cell.interrupted || cell.stopped}
235
235
  />
236
236
  );
237
237
 
@@ -294,10 +294,9 @@ export const GridLayoutRenderer: React.FC<Props> = ({
294
294
  mode={mode}
295
295
  cellId={cell.id}
296
296
  output={cell.output}
297
- status={cell.status}
298
- stale={outputIsStale(cell, false)}
297
+ staleInputs={cell.staleInputs}
298
+ hideWithoutValue={false}
299
299
  isScrollable={false}
300
- hidden={false}
301
300
  />
302
301
  );
303
302
  })}
@@ -364,10 +363,9 @@ export const GridLayoutRenderer: React.FC<Props> = ({
364
363
  mode={mode}
365
364
  cellId={cell.id}
366
365
  output={cell.output}
366
+ staleInputs={cell.staleInputs}
367
+ hideWithoutValue={false}
367
368
  isScrollable={false}
368
- status={cell.status}
369
- stale={outputIsStale(cell, false)}
370
- hidden={false}
371
369
  />
372
370
  </div>
373
371
  ))}
@@ -377,31 +375,39 @@ export const GridLayoutRenderer: React.FC<Props> = ({
377
375
  );
378
376
  };
379
377
 
380
- interface GridCellProps extends Pick<CellRuntimeState, "output" | "status"> {
378
+ interface GridCellProps extends Pick<
379
+ CellRuntimeState,
380
+ "output" | "staleInputs"
381
+ > {
381
382
  className?: string;
382
383
  code: string;
383
384
  cellId: CellId;
384
385
  mode: AppMode;
385
- hidden: boolean;
386
+ /** Whether a cell whose last run ended without a value keeps its place but shows nothing. */
387
+ hideWithoutValue: boolean;
386
388
  isScrollable: boolean;
387
389
  side?: GridLayoutCellSide;
388
- stale: boolean;
389
390
  }
390
391
 
391
392
  const GridCell = memo(
392
393
  ({
393
394
  output,
395
+ staleInputs,
394
396
  cellId,
395
- status,
396
397
  mode,
397
398
  code,
398
- hidden,
399
+ hideWithoutValue,
399
400
  isScrollable,
400
401
  side,
401
402
  className,
402
- stale,
403
403
  }: GridCellProps) => {
404
- const loading = outputIsLoading(status);
404
+ const execution = useCellExecution(cellId);
405
+ const loading = outputIsLoading(execution.status);
406
+ const stale = outputIsStale(execution, staleInputs, false);
407
+ const hidden =
408
+ hideWithoutValue &&
409
+ execution.outcome !== null &&
410
+ execution.outcome !== "value";
405
411
 
406
412
  const isOutputEmpty = output == null || output.data === "";
407
413
  // If not reading, show code when there is no output
@@ -15,7 +15,6 @@ function makeCell(id: string, code = "print('hi')"): CellData {
15
15
  code,
16
16
  edited: false,
17
17
  lastCodeRun: null,
18
- lastExecutionTime: null,
19
18
  config: { hide_code: false, disabled: false, column: null },
20
19
  };
21
20
  }