@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
@@ -3,7 +3,7 @@
3
3
  import { render } from "@testing-library/react";
4
4
  import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
5
5
  import { TooltipProvider } from "@/components/ui/tooltip";
6
- import { formatElapsedTime, type Seconds } from "@/utils/time";
6
+ import { formatElapsedTime, type Milliseconds } from "@/utils/time";
7
7
  import {
8
8
  CellStatusComponent,
9
9
  ElapsedTime,
@@ -76,8 +76,8 @@ describe("CellStatusComponent", () => {
76
76
  status: "idle" as const,
77
77
  interrupted: false,
78
78
  elapsedTime: null,
79
- runStartTimestamp: null,
80
- lastRunStartTimestamp: null,
79
+ startedAt: null,
80
+ blocked: false,
81
81
  uninstantiated: false,
82
82
  };
83
83
 
@@ -95,7 +95,7 @@ describe("CellStatusComponent", () => {
95
95
  ...baseProps,
96
96
  disabled: true,
97
97
  staleInputs: true,
98
- lastRunStartTimestamp: 1_704_096_000 as Seconds, // Jan 1, 2024 12:00:00
98
+ startedAt: 1_704_096_000_000 as Milliseconds, // Jan 1, 2024 12:00:00
99
99
  };
100
100
 
101
101
  const { container } = render(
@@ -110,7 +110,7 @@ describe("CellStatusComponent", () => {
110
110
  const props = {
111
111
  ...baseProps,
112
112
  disabled: true,
113
- lastRunStartTimestamp: 1_704_096_000 as Seconds,
113
+ startedAt: 1_704_096_000_000 as Milliseconds,
114
114
  };
115
115
 
116
116
  const { container } = render(
@@ -124,8 +124,8 @@ describe("CellStatusComponent", () => {
124
124
  test("renders disabled transitively state", () => {
125
125
  const props = {
126
126
  ...baseProps,
127
- status: "disabled-transitively" as const,
128
- lastRunStartTimestamp: 1_704_096_000 as Seconds,
127
+ blocked: true,
128
+ startedAt: 1_704_096_000_000 as Milliseconds,
129
129
  };
130
130
 
131
131
  const { container } = render(
@@ -139,9 +139,9 @@ describe("CellStatusComponent", () => {
139
139
  test("renders stale and disabled transitively state", () => {
140
140
  const props = {
141
141
  ...baseProps,
142
- status: "disabled-transitively" as const,
142
+ blocked: true,
143
143
  staleInputs: true,
144
- lastRunStartTimestamp: 1_704_096_000 as Seconds,
144
+ startedAt: 1_704_096_000_000 as Milliseconds,
145
145
  };
146
146
 
147
147
  const { container } = render(
@@ -156,8 +156,7 @@ describe("CellStatusComponent", () => {
156
156
  const props = {
157
157
  ...baseProps,
158
158
  status: "running" as const,
159
- runStartTimestamp: 1_704_096_000 as Seconds,
160
- lastRunStartTimestamp: 1_704_096_000 as Seconds,
159
+ startedAt: 1_704_096_000_000 as Milliseconds,
161
160
  };
162
161
 
163
162
  const { container } = render(
@@ -172,7 +171,7 @@ describe("CellStatusComponent", () => {
172
171
  const props = {
173
172
  ...baseProps,
174
173
  status: "queued" as const,
175
- lastRunStartTimestamp: 1_704_096_000 as Seconds,
174
+ startedAt: 1_704_096_000_000 as Milliseconds,
176
175
  };
177
176
 
178
177
  const { container } = render(
@@ -201,8 +200,8 @@ describe("CellStatusComponent", () => {
201
200
  const props = {
202
201
  ...baseProps,
203
202
  interrupted: true,
204
- elapsedTime: 1500,
205
- lastRunStartTimestamp: 1_704_096_000 as Seconds,
203
+ elapsedTime: 1500 as Milliseconds,
204
+ startedAt: 1_704_096_000_000 as Milliseconds,
206
205
  };
207
206
 
208
207
  const { container } = render(
@@ -217,8 +216,8 @@ describe("CellStatusComponent", () => {
217
216
  const props = {
218
217
  ...baseProps,
219
218
  edited: true,
220
- elapsedTime: 1500,
221
- lastRunStartTimestamp: 1_704_096_000 as Seconds,
219
+ elapsedTime: 1500 as Milliseconds,
220
+ startedAt: 1_704_096_000_000 as Milliseconds,
222
221
  };
223
222
 
224
223
  const { container } = render(
@@ -233,8 +232,8 @@ describe("CellStatusComponent", () => {
233
232
  const props = {
234
233
  ...baseProps,
235
234
  staleInputs: true,
236
- elapsedTime: 1500,
237
- lastRunStartTimestamp: 1_704_096_000 as Seconds,
235
+ elapsedTime: 1500 as Milliseconds,
236
+ startedAt: 1_704_096_000_000 as Milliseconds,
238
237
  };
239
238
 
240
239
  const { container } = render(
@@ -248,8 +247,8 @@ describe("CellStatusComponent", () => {
248
247
  test("renders completed run with elapsed time", () => {
249
248
  const props = {
250
249
  ...baseProps,
251
- elapsedTime: 1500,
252
- lastRunStartTimestamp: 1_704_096_000 as Seconds,
250
+ elapsedTime: 1500 as Milliseconds,
251
+ startedAt: 1_704_096_000_000 as Milliseconds,
253
252
  };
254
253
 
255
254
  const { container } = render(
@@ -7,11 +7,12 @@ import {
7
7
  cellGraphsAtom,
8
8
  isVariableAffectedBySelectedCell,
9
9
  } from "@/components/editor/chrome/wrapper/minimap-state";
10
- import { useCellData, useCellRuntime } from "@/core/cells/cells";
10
+ import { useCellData } from "@/core/cells/cells";
11
11
  import { useCellActions } from "@/core/cells/actions";
12
12
  import { cellFocusAtom, useCellFocusActions } from "@/core/cells/focus";
13
13
  import type { CellId } from "@/core/cells/ids";
14
14
  import { useLayout } from "@/core/cells/layout";
15
+ import { useCellExecution } from "@/core/executions";
15
16
  import { useVariables } from "@/core/variables/state";
16
17
  import { cn } from "@/utils/cn";
17
18
  import { extractCellPreview } from "./utils/cell-preview";
@@ -32,7 +33,7 @@ const MinimapCell: React.FC<MinimapCellProps> = (props) => {
32
33
  id: props.cellId,
33
34
  graph: graphs[props.cellId],
34
35
  code: useCellData(props.cellId).code,
35
- hasError: useCellRuntime(props.cellId).errored,
36
+ hasError: useCellExecution(props.cellId).outcome === "error",
36
37
  };
37
38
 
38
39
  let selectedCell: undefined | { id: CellId; graph: CellGraph };
@@ -104,16 +104,9 @@ describe("Cell data attributes", () => {
104
104
  });
105
105
 
106
106
  notebook.cellRuntime[cid] = createCellRuntimeState({
107
- status: "idle",
108
107
  output: null,
109
108
  consoleOutputs: [],
110
- interrupted: false,
111
- errored: false,
112
- stopped: false,
113
109
  staleInputs: false,
114
- runStartTimestamp: null,
115
- lastRunStartTimestamp: null,
116
- runElapsedTimeMs: null,
117
110
  debuggerActive: false,
118
111
  outline: null,
119
112
  });
@@ -4,6 +4,7 @@ import { createStore, Provider } from "jotai";
4
4
  import { beforeAll, describe, expect, it } from "vitest";
5
5
  import { SetupMocks } from "@/__mocks__/common";
6
6
  import { mockDocumentModel } from "@/__mocks__/document";
7
+ import { MockExecutions } from "@/__mocks__/executions";
7
8
  import { MockNotebook } from "@/__mocks__/notebook";
8
9
  import { MockRequestClient } from "@/__mocks__/requests";
9
10
  import { cellId } from "@/__tests__/branded";
@@ -50,7 +51,6 @@ function seedNotebook(
50
51
  },
51
52
  });
52
53
  notebook.cellRuntime[cid] = createCellRuntimeState({
53
- status: "idle",
54
54
  output: {
55
55
  channel: "output",
56
56
  mimetype: "text/html",
@@ -207,8 +207,8 @@ describe("output persistence across mode changes", () => {
207
207
 
208
208
  it("hides errored cells while presenting without unmounting them", () => {
209
209
  const { store, wrapper } = createTestWrapper();
210
+ MockExecutions.seed(store, [MockExecutions.errored(cid)]);
210
211
  seedNotebook(store, {
211
- errored: true,
212
212
  output: {
213
213
  channel: "marimo-error",
214
214
  mimetype: "application/vnd.marimo+error",
@@ -52,6 +52,7 @@ function renderActions(cellId: CellId, name: string, edit?: Edit) {
52
52
  name,
53
53
  config: { disabled: false, hide_code: false, column: null },
54
54
  status: "idle",
55
+ blocked: false,
55
56
  hasOutput: false,
56
57
  hasConsoleOutput: false,
57
58
  getEditorView: () => null,
@@ -56,7 +56,8 @@ import {
56
56
  appWidthAtom,
57
57
  autoInstantiateAtom,
58
58
  } from "@/core/config/config";
59
- import type { CellConfig, RuntimeState } from "@/core/network/types";
59
+ import type { CellRunStatus } from "@/core/executions";
60
+ import type { CellConfig } from "@/core/network/types";
60
61
  import { canEditAtom } from "@/core/notebook";
61
62
  import { canLinkToCell, createCellLink } from "@/utils/cell-urls";
62
63
  import { copyToClipboard } from "@/utils/copy";
@@ -74,7 +75,9 @@ export interface CellActionButtonProps extends Pick<
74
75
  "name" | "config"
75
76
  > {
76
77
  cellId: CellId;
77
- status: RuntimeState;
78
+ status: CellRunStatus;
79
+ /** Whether a cell it depends on is disabled. */
80
+ blocked: boolean;
78
81
  hasOutput: boolean;
79
82
  hasConsoleOutput: boolean;
80
83
  getEditorView: () => EditorView | null;
@@ -121,6 +124,7 @@ export function useCellActionButtons({ cell, closePopover }: Props) {
121
124
  hasOutput,
122
125
  hasConsoleOutput,
123
126
  status,
127
+ blocked,
124
128
  } = cell;
125
129
 
126
130
  const toggleDisabled = () => {
@@ -155,7 +159,7 @@ export function useCellActionButtons({ cell, closePopover }: Props) {
155
159
  hidden:
156
160
  status === "running" ||
157
161
  status === "queued" ||
158
- status === "disabled-transitively" ||
162
+ blocked ||
159
163
  config.disabled,
160
164
  redundant: true,
161
165
  handle: () => runCell(),
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { atom, useAtomValue } from "jotai";
4
4
  import { notebookAtom } from "@/core/cells/cells";
5
+ import { cellExecutionInputsAtom, cellExecutionView } from "@/core/executions";
5
6
  import { Logger } from "@/utils/Logger";
6
7
  import { Button } from "../../ui/button";
7
8
  import { Kbd } from "../../ui/kbd";
@@ -10,12 +11,13 @@ import { FloatingAlert } from "./floating-alert";
10
11
  // Atom to check if there is a cell with unresolved stdin
11
12
  const hasBlockingStdinAtom = atom((get) => {
12
13
  const notebook = get(notebookAtom);
14
+ const executions = get(cellExecutionInputsAtom);
13
15
 
14
16
  // Check each cell in order
15
17
  for (const cellId of notebook.cellIds) {
16
18
  // Check if the cell is idle and has unresolved stdin
17
19
  const runtime = notebook.cellRuntime[cellId];
18
- if (runtime.status === "idle") {
20
+ if (cellExecutionView(executions, cellId).status === "idle") {
19
21
  const hasUnresolvedStdin = runtime.consoleOutputs.some(
20
22
  (output) => output.channel === "stdin" && output.response === undefined,
21
23
  );
@@ -7,24 +7,29 @@ import {
7
7
  } from "lucide-react";
8
8
  import { useDateFormatter } from "react-aria";
9
9
  import { MultiIcon } from "@/components/icons/multi-icon";
10
+ import type { CellRunStatus } from "@/core/executions";
10
11
  import { Logger } from "@/utils/Logger";
11
- import type { CellRuntimeState } from "../../../core/cells/types";
12
12
  import { useElapsedTime } from "../../../hooks/useElapsedTime";
13
13
  import { Tooltip } from "../../ui/tooltip";
14
14
 
15
15
  import "./cell-status.css";
16
16
  import { formatDistanceToNow } from "date-fns";
17
- import { formatElapsedTime, Time } from "@/utils/time";
17
+ import { formatElapsedTime, type Milliseconds, Time } from "@/utils/time";
18
18
 
19
- export interface CellStatusComponentProps extends Pick<
20
- CellRuntimeState,
21
- "status" | "runStartTimestamp" | "interrupted" | "lastRunStartTimestamp"
22
- > {
19
+ export interface CellStatusComponentProps {
23
20
  editing: boolean;
24
21
  edited: boolean;
25
22
  disabled: boolean;
23
+ /** Whether a cell it depends on is disabled. */
24
+ blocked: boolean;
26
25
  staleInputs: boolean;
27
- elapsedTime: number | null;
26
+ status: CellRunStatus;
27
+ /** Whether the cell's last run was interrupted. */
28
+ interrupted: boolean;
29
+ /** How long the last run took, once it has ended. */
30
+ elapsedTime: Milliseconds | null;
31
+ /** When the current run started, or the last one, since the epoch. */
32
+ startedAt: Milliseconds | null;
28
33
  uninstantiated: boolean;
29
34
  }
30
35
 
@@ -32,20 +37,21 @@ export const CellStatusComponent: React.FC<CellStatusComponentProps> = ({
32
37
  editing,
33
38
  status,
34
39
  disabled,
40
+ blocked,
35
41
  staleInputs,
36
42
  edited,
37
43
  interrupted,
38
44
  elapsedTime,
39
- runStartTimestamp,
40
- lastRunStartTimestamp,
45
+ startedAt,
41
46
  uninstantiated,
42
47
  }) => {
43
48
  if (!editing) {
44
49
  return null;
45
50
  }
46
51
 
47
- const start = runStartTimestamp ?? lastRunStartTimestamp;
48
- const lastRanTime = start ? <LastRanTime lastRanTime={start} /> : null;
52
+ const lastRanTime = startedAt ? (
53
+ <LastRanTime lastRanTime={startedAt} />
54
+ ) : null;
49
55
 
50
56
  // unexpected states
51
57
  if (disabled && status === "running") {
@@ -103,7 +109,7 @@ export const CellStatusComponent: React.FC<CellStatusComponentProps> = ({
103
109
  }
104
110
 
105
111
  // disabled from parent
106
- if (!staleInputs && status === "disabled-transitively") {
112
+ if (!staleInputs && blocked) {
107
113
  return (
108
114
  <Tooltip
109
115
  content={
@@ -131,7 +137,7 @@ export const CellStatusComponent: React.FC<CellStatusComponentProps> = ({
131
137
  }
132
138
 
133
139
  // stale from parent being disabled
134
- if (staleInputs && status === "disabled-transitively") {
140
+ if (staleInputs && blocked) {
135
141
  return (
136
142
  <Tooltip
137
143
  content={
@@ -175,9 +181,7 @@ export const CellStatusComponent: React.FC<CellStatusComponentProps> = ({
175
181
  data-testid="cell-status"
176
182
  data-status="running"
177
183
  >
178
- <CellTimer
179
- startTime={Time.fromSeconds(runStartTimestamp) || Time.now()}
180
- />
184
+ <CellTimer startTime={startedAt ?? Time.now().toMilliseconds()} />
181
185
  </div>
182
186
  </Tooltip>
183
187
  );
@@ -307,7 +311,7 @@ export const ElapsedTime = (props: { elapsedTime: string }) => {
307
311
  );
308
312
  };
309
313
  const LastRanTime = (props: { lastRanTime: number }) => {
310
- const date = new Date(props.lastRanTime * 1000);
314
+ const date = new Date(props.lastRanTime);
311
315
  const today = new Date();
312
316
 
313
317
  // Looks like HH:MM:SS.SSS AM/PM
@@ -348,7 +352,7 @@ const LastRanTime = (props: { lastRanTime: number }) => {
348
352
  );
349
353
  };
350
354
 
351
- const CellTimer = (props: { startTime: Time }) => {
352
- const time = useElapsedTime(props.startTime.toMilliseconds());
355
+ const CellTimer = (props: { startTime: Milliseconds }) => {
356
+ const time = useElapsedTime(props.startTime);
353
357
  return <span>{formatElapsedTime(time)}</span>;
354
358
  };
@@ -1,7 +1,8 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  import { HardDriveDownloadIcon, PlayIcon } from "lucide-react";
3
3
  import type { JSX } from "react";
4
- import type { CellConfig, RuntimeState } from "@/core/network/types";
4
+ import type { CellRunStatus } from "@/core/executions";
5
+ import type { CellConfig } from "@/core/network/types";
5
6
  import {
6
7
  getConnectionTooltip,
7
8
  isAppInteractionDisabled,
@@ -35,20 +36,29 @@ function computeColor({
35
36
 
36
37
  export const RunButton = (props: {
37
38
  edited: boolean;
38
- status: RuntimeState;
39
+ status: CellRunStatus;
40
+ /** Whether a cell it depends on is disabled. */
41
+ blocked: boolean;
39
42
  needsRun: boolean;
40
43
  connectionState: ConnectionState;
41
44
  config: CellConfig;
42
45
  onClick?: () => void;
43
46
  }): JSX.Element => {
44
- const { onClick, connectionState, needsRun, status, config, edited } = props;
47
+ const {
48
+ onClick,
49
+ connectionState,
50
+ needsRun,
51
+ status,
52
+ blocked,
53
+ config,
54
+ edited,
55
+ } = props;
45
56
 
46
- const blockedStatus = status === "disabled-transitively";
47
57
  const loading = status === "running" || status === "queued";
48
58
  const inactive =
49
59
  isAppInteractionDisabled(connectionState) ||
50
60
  loading ||
51
- (!config.disabled && blockedStatus && !edited);
61
+ (!config.disabled && blocked && !edited);
52
62
  const variant = computeColor({
53
63
  connectionState,
54
64
  needsRun,
@@ -69,7 +79,7 @@ export const RunButton = (props: {
69
79
  </ToolbarItem>
70
80
  );
71
81
  }
72
- if (!config.disabled && blockedStatus && !edited) {
82
+ if (!config.disabled && blocked && !edited) {
73
83
  return (
74
84
  <ToolbarItem
75
85
  disabled={inactive}
@@ -0,0 +1,45 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { MockExecutions } from "@/__mocks__/executions";
5
+ import { MockNotebook } from "@/__mocks__/notebook";
6
+ import { cellId } from "@/__tests__/branded";
7
+ import {
8
+ cellExecutionInputsAtom,
9
+ cellExecutionView,
10
+ notebookQueuedOrRunningCountAtom,
11
+ } from "@/core/executions";
12
+ import { store } from "@/core/state/jotai";
13
+ import { runCells } from "../useRunCells";
14
+
15
+ const ENABLED = cellId("enabled");
16
+ const DISABLED = cellId("disabled");
17
+
18
+ describe("runCells", () => {
19
+ it("sends a disabled cell to the kernel without reading it as queued", async () => {
20
+ MockExecutions.seed(store, []);
21
+ const notebook = MockNotebook.notebookState({
22
+ cellData: {
23
+ [ENABLED]: { code: "x = 1" },
24
+ [DISABLED]: { code: "y = 2", config: { disabled: true } },
25
+ },
26
+ });
27
+ const sendRun = vi.fn(async () => null);
28
+
29
+ await runCells({
30
+ cellIds: [ENABLED, DISABLED],
31
+ sendRun,
32
+ prepareForRun: vi.fn(),
33
+ notebook,
34
+ });
35
+
36
+ expect(sendRun).toHaveBeenCalledWith({
37
+ cellIds: [ENABLED, DISABLED],
38
+ codes: ["x = 1", "y = 2"],
39
+ });
40
+ const inputs = store.get(cellExecutionInputsAtom);
41
+ expect(cellExecutionView(inputs, ENABLED).status).toBe("queued");
42
+ expect(cellExecutionView(inputs, DISABLED).status).toBe("idle");
43
+ expect(store.get(notebookQueuedOrRunningCountAtom)).toBe(1);
44
+ });
45
+ });
@@ -23,6 +23,7 @@ import { toast } from "@/components/ui/use-toast";
23
23
  import { useCellData, useCellRuntime } from "@/core/cells/cells";
24
24
  import { CellOutputId } from "@/core/cells/ids";
25
25
  import { isOutputEmpty } from "@/core/cells/outputs";
26
+ import { useCellExecution } from "@/core/executions";
26
27
  import { goToDefinitionWithLspFallback } from "@/core/codemirror/go-to-definition/utils";
27
28
  import { sendToPanelManager } from "@/core/vscode/vscode-bindings";
28
29
  import { copyImageToClipboard, copyToClipboard } from "@/utils/copy";
@@ -54,12 +55,14 @@ export const CellActionsContextMenu = ({
54
55
  }: Props) => {
55
56
  const cellData = useCellData(cellId);
56
57
  const cellRuntime = useCellRuntime(cellId);
58
+ const execution = useCellExecution(cellId);
57
59
  const actions = useCellActionButtons({
58
60
  cell: {
59
61
  cellId: cellId,
60
62
  name: cellData.name,
61
63
  config: cellData.config,
62
- status: cellRuntime.status,
64
+ status: execution.status,
65
+ blocked: execution.blocked,
63
66
  hasOutput: !isOutputEmpty(cellRuntime.output),
64
67
  hasConsoleOutput: cellRuntime.consoleOutputs.length > 0,
65
68
  getEditorView,
@@ -19,7 +19,8 @@ import {
19
19
  useNotebookDocumentEdit,
20
20
  } from "@/core/notebook/document";
21
21
  import { usePendingDeleteService } from "@/core/cells/pending-delete-service";
22
- import type { CellData, CellRuntimeState } from "@/core/cells/types";
22
+ import type { CellData } from "@/core/cells/types";
23
+ import type { CellRunStatus } from "@/core/executions";
23
24
  import { setupCodeMirror } from "@/core/codemirror/cm";
24
25
  import { acceptCompletionOnEnterAtom } from "@/core/codemirror/completion/accept-on-enter-atom";
25
26
  import { editorMountScheduler } from "@/core/codemirror/editor-mount-scheduler";
@@ -54,10 +55,11 @@ import { useSplitCellCallback } from "../useSplitCell";
54
55
  import { CodePlaceholder } from "./code-placeholder";
55
56
  import { LanguageToggles } from "./language-toggle";
56
57
 
57
- export interface CellEditorProps
58
- extends
59
- Pick<CellRuntimeState, "status">,
60
- Pick<CellData, "id" | "code" | "config"> {
58
+ export interface CellEditorProps extends Pick<
59
+ CellData,
60
+ "id" | "code" | "config"
61
+ > {
62
+ status: CellRunStatus;
61
63
  runCell: () => void;
62
64
  theme: Theme;
63
65
  showPlaceholder: boolean;
@@ -9,6 +9,7 @@ import {
9
9
  RefreshCcw,
10
10
  } from "lucide-react";
11
11
  import type React from "react";
12
+ import { useAtomValue } from "jotai";
12
13
  import { memo } from "react";
13
14
  import { Toolbar } from "@/components/layout/toolbar";
14
15
  import { Button } from "@/components/ui/button";
@@ -17,6 +18,7 @@ import { useNotebook } from "@/core/cells/cells";
17
18
  import { columnOf, useLayout } from "@/core/cells/layout";
18
19
  import type { CellId } from "@/core/cells/ids";
19
20
  import { getDescendantsStatus } from "@/core/cells/utils";
21
+ import { cellExecutionInputsAtom } from "@/core/executions";
20
22
  import { cn } from "@/utils/cn";
21
23
 
22
24
  interface Props {
@@ -65,9 +67,11 @@ export const CollapsedCellBanner: React.FC<{
65
67
  count: number;
66
68
  }> = memo(({ onClick, count, cellId }) => {
67
69
  const notebook = useNotebook();
70
+ const executions = useAtomValue(cellExecutionInputsAtom);
68
71
  const layout = useLayout();
69
72
  const states = getDescendantsStatus(
70
73
  notebook,
74
+ executions,
71
75
  columnOf(layout, cellId).folds.get(cellId) ?? [],
72
76
  );
73
77
 
@@ -7,10 +7,16 @@ import { getNotebook, type NotebookState } from "@/core/cells/cells";
7
7
  import { useCellActions } from "@/core/cells/actions";
8
8
  import type { CellId } from "@/core/cells/ids";
9
9
  import { enabledCellIds, staleCellIds } from "@/core/cells/utils";
10
+ import {
11
+ cellExecutionInputsAtom,
12
+ submitCells,
13
+ withdrawCells,
14
+ } from "@/core/executions";
10
15
  import { getCurrentLanguageAdapter } from "@/core/codemirror/language/commands";
11
16
  import { getEditorCodeAsPython } from "@/core/codemirror/language/utils";
12
17
  import { useRequestClient } from "@/core/network/requests";
13
18
  import type { ExecuteCellsRequest } from "@/core/network/types";
19
+ import { store } from "@/core/state/jotai";
14
20
  import { Logger } from "@/utils/Logger";
15
21
 
16
22
  export const hasRunAnyCellAtom = atom<boolean>(false);
@@ -20,7 +26,9 @@ export const hasRunAnyCellAtom = atom<boolean>(false);
20
26
  */
21
27
  export function useRunStaleCells() {
22
28
  const runCells = useRunCells();
23
- const runStaleCells = useEvent(() => runCells(staleCellIds(getNotebook())));
29
+ const runStaleCells = useEvent(() =>
30
+ runCells(staleCellIds(getNotebook(), store.get(cellExecutionInputsAtom))),
31
+ );
24
32
  return runStaleCells;
25
33
  }
26
34
 
@@ -107,9 +115,16 @@ export async function runCells({
107
115
  codes.push(code);
108
116
  prepareForRun({ cellId });
109
117
  }
110
-
111
- // Send the run request to the Kernel
118
+ // The kernel sets a disabled cell aside without planning an execution,
119
+ // so a submission for it would never be superseded.
120
+ submitCells(
121
+ store,
122
+ cellIds.filter((cellId) => !cellData[cellId]?.config.disabled),
123
+ );
124
+
125
+ // TODO(MO-8346): Submit through the host's `runtime.run`.
112
126
  await sendRun({ cellIds: cellIds, codes: codes }).catch((error) => {
127
+ withdrawCells(store, cellIds);
113
128
  Logger.error(error);
114
129
  });
115
130
  }
@@ -7,10 +7,8 @@ import type { PropsWithChildren } from "react";
7
7
  import { useEffect, useMemo } from "react";
8
8
  import { ReorderableList } from "@/components/ui/reorderable-list";
9
9
  import { Tooltip } from "@/components/ui/tooltip";
10
- import {
11
- cellErrorCount,
12
- notebookQueuedOrRunningCountAtom,
13
- } from "@/core/cells/cells";
10
+ import { cellErrorCount } from "@/core/cells/cells";
11
+ import { notebookQueuedOrRunningCountAtom } from "@/core/executions";
14
12
  import { capabilitiesAtom } from "@/core/config/capabilities";
15
13
  import { aiEnabledAtom } from "@/core/config/config";
16
14
  import { cn } from "@/utils/cn";
@@ -5,7 +5,7 @@ import { HourglassIcon, UnlinkIcon } from "lucide-react";
5
5
  import React from "react";
6
6
  import { Tooltip } from "@/components/ui/tooltip";
7
7
  import { notebookScrollToRunning } from "@/core/cells/actions";
8
- import { onlyScratchpadIsRunningAtom } from "@/core/cells/cells";
8
+ import { cellExecutionInputsAtom, onlyScratchRunning } from "@/core/executions";
9
9
  import { viewStateAtom } from "@/core/mode";
10
10
  import {
11
11
  type ConnectionStatus,
@@ -77,7 +77,9 @@ const DisconnectedIcon: React.FC<{ onReconnect?: () => void }> = ({
77
77
  };
78
78
 
79
79
  const RunningIcon = () => {
80
- const scratchpadOnly = useAtomValue(onlyScratchpadIsRunningAtom);
80
+ const scratchpadOnly = onlyScratchRunning(
81
+ useAtomValue(cellExecutionInputsAtom).executions,
82
+ );
81
83
  const tooltip = scratchpadOnly
82
84
  ? "Scratchpad is running"
83
85
  : "Jump to running cell";
@@ -22,6 +22,7 @@ import {
22
22
  visibleNeighbor,
23
23
  } from "@/core/cells/layout";
24
24
  import { useCellActions } from "@/core/cells/actions";
25
+ import { cellExecutionInputsAtom, cellExecutionView } from "@/core/executions";
25
26
  import { useNotebookDocumentEdit } from "@/core/notebook/document";
26
27
  import { useCellFocusActions } from "@/core/cells/focus";
27
28
  import type { CellId } from "@/core/cells/ids";
@@ -582,9 +583,9 @@ export function useCellNavigationProps(
582
583
  selectedCells.size >= 2 ? [...selectedCells] : [cellId];
583
584
 
584
585
  // Cannot delete running cells
585
- const notebook = store.get(notebookAtom);
586
+ const executions = store.get(cellExecutionInputsAtom);
586
587
  const hasRunningCell = cellIds.some((id) => {
587
- const { status } = notebook.cellRuntime[id];
588
+ const { status } = cellExecutionView(executions, id);
588
589
  return status === "running" || status === "queued";
589
590
  });
590
591