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

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 (140) hide show
  1. package/dist/{ConnectedDataExplorerComponent-C4yCN38h.js → ConnectedDataExplorerComponent-eLHPh6uu.js} +8 -8
  2. package/dist/{ErrorBoundary-CewXPyL5.js → ErrorBoundary-DCtynjHZ.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-BWIkve2R.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-CMF5tcNj.js} +44 -47
  14. package/dist/{common-N3Jd_6m9.js → common-DYAjQ1bK.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-B6oND8pX.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-BelKx6R5.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-CMZ-kv0S.js} +11 -11
  33. package/dist/{glide-utils-DC3sk6EX.js → glide-utils-BSw3SSHF.js} +1 -1
  34. package/dist/{globals-B2_uMYDY.js → globals-DV77npCG.js} +2 -2
  35. package/dist/{html-to-image-S7spRQCi.js → html-to-image-Bc2AvXHG.js} +5121 -5121
  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-CFTuKnrY.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-qecD7Dk8.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-Doix35eL.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/RenderHTML.tsx +3 -5
  135. package/src/plugins/core/__test__/RenderHTML-copy.test.tsx +60 -0
  136. package/src/plugins/core/registerReactComponent.tsx +4 -13
  137. package/src/stories/cell-status.stories.tsx +17 -10
  138. package/src/stories/cell.stories.tsx +0 -31
  139. package/src/stories/layout/vertical/one-column.stories.tsx +0 -9
  140. package/src/utils/time.ts +10 -0
@@ -1,6 +1,7 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  /* oxlint-disable typescript/no-explicit-any */
3
3
  import { describe, expect, it } from "vitest";
4
+ import { MockExecutions } from "@/__mocks__/executions";
4
5
  import { initialNotebookState, type NotebookState } from "@/core/cells/cells";
5
6
  import { CellId } from "@/core/cells/ids";
6
7
  import type { CellData } from "@/core/cells/types";
@@ -13,278 +14,105 @@ import {
13
14
  staleCellIds,
14
15
  } from "../utils";
15
16
 
16
- describe("staleCellIds", () => {
17
- it("should return cell IDs that have not been instantiated when auto-instantiate is false", () => {
18
- const state: NotebookState = {
19
- cellIds: ["cell1", "cell2"],
20
- cellData: {
21
- cell1: {
22
- lastExecutionTime: null,
23
- edited: false,
24
- config: { disabled: false },
25
- },
26
- cell2: {
27
- lastExecutionTime: 123,
28
- edited: false,
29
- config: { disabled: false },
30
- },
31
- },
32
- cellRuntime: {
33
- cell1: {
34
- runElapsedTimeMs: null,
35
- status: "idle",
36
- errored: false,
37
- interrupted: false,
38
- stopped: false,
39
- staleInputs: false,
40
- },
41
- cell2: {
42
- runElapsedTimeMs: 456,
43
- status: "idle",
44
- errored: false,
45
- interrupted: false,
46
- stopped: false,
47
- staleInputs: false,
48
- },
49
- },
50
- } as any;
51
-
52
- const result = staleCellIds(state);
53
-
54
- expect(result).toEqual(["cell1"]);
55
- });
56
-
57
- it("should use lastExecutionTime when runElapsedTimeMs is null", () => {
58
- const state: NotebookState = {
59
- cellIds: ["cell1", "cell2"],
60
- cellData: {
61
- cell1: {
62
- lastExecutionTime: null,
63
- edited: false,
64
- config: { disabled: false },
65
- },
66
- cell2: {
67
- lastExecutionTime: 123,
68
- edited: false,
69
- config: { disabled: false },
70
- },
71
- },
72
- cellRuntime: {
73
- cell1: {
74
- runElapsedTimeMs: null,
75
- status: "idle",
76
- errored: false,
77
- interrupted: false,
78
- stopped: false,
79
- staleInputs: false,
80
- },
81
- cell2: {
82
- runElapsedTimeMs: null,
83
- status: "idle",
84
- errored: false,
85
- interrupted: false,
86
- stopped: false,
87
- staleInputs: false,
88
- },
89
- },
90
- } as any;
17
+ function notebook(
18
+ cells: Record<
19
+ string,
20
+ { edited?: boolean; disabled?: boolean; staleInputs?: boolean }
21
+ >,
22
+ ): NotebookState {
23
+ return {
24
+ cellIds: Object.keys(cells),
25
+ cellData: Object.fromEntries(
26
+ Object.entries(cells).map(([id, cell]) => [
27
+ id,
28
+ { edited: cell.edited ?? false, config: { disabled: cell.disabled } },
29
+ ]),
30
+ ),
31
+ cellRuntime: Object.fromEntries(
32
+ Object.entries(cells).map(([id, cell]) => [
33
+ id,
34
+ { staleInputs: cell.staleInputs ?? false },
35
+ ]),
36
+ ),
37
+ } as any;
38
+ }
91
39
 
92
- const result = staleCellIds(state);
40
+ describe("staleCellIds", () => {
41
+ it("should return cell IDs that have never run", () => {
42
+ const state = notebook({ cell1: {}, cell2: {} });
43
+ const executions = MockExecutions.inputs([
44
+ MockExecutions.completed("cell2"),
45
+ ]);
93
46
 
94
- expect(result).toEqual(["cell1"]);
47
+ expect(staleCellIds(state, executions)).toEqual(["cell1"]);
95
48
  });
96
49
 
97
50
  it("should return cell IDs that are edited", () => {
98
- const state: NotebookState = {
99
- cellIds: ["cell1", "cell2"],
100
- cellData: {
101
- cell1: {
102
- lastExecutionTime: 123,
103
- edited: true,
104
- config: { disabled: false },
105
- },
106
- cell2: {
107
- lastExecutionTime: 123,
108
- edited: false,
109
- config: { disabled: false },
110
- },
111
- },
112
- cellRuntime: {
113
- cell1: {
114
- runElapsedTimeMs: 456,
115
- status: "idle",
116
- errored: false,
117
- interrupted: false,
118
- stopped: false,
119
- staleInputs: false,
120
- },
121
- cell2: {
122
- runElapsedTimeMs: 456,
123
- status: "idle",
124
- errored: false,
125
- interrupted: false,
126
- stopped: false,
127
- staleInputs: false,
128
- },
129
- },
130
- } as any;
131
-
132
- const result = staleCellIds(state);
51
+ const state = notebook({ cell1: { edited: true }, cell2: {} });
52
+ const executions = MockExecutions.inputs([
53
+ MockExecutions.completed("cell1"),
54
+ MockExecutions.completed("cell2"),
55
+ ]);
133
56
 
134
- expect(result).toEqual(["cell1"]);
57
+ expect(staleCellIds(state, executions)).toEqual(["cell1"]);
135
58
  });
136
59
 
137
60
  it("should return cell IDs that are interrupted", () => {
138
- const state: NotebookState = {
139
- cellIds: ["cell1", "cell2"],
140
- cellData: {
141
- cell1: {
142
- lastExecutionTime: 123,
143
- edited: false,
144
- config: { disabled: false },
145
- },
146
- cell2: {
147
- lastExecutionTime: 123,
148
- edited: false,
149
- config: { disabled: false },
150
- },
151
- },
152
- cellRuntime: {
153
- cell1: {
154
- runElapsedTimeMs: 456,
155
- status: "idle",
156
- errored: false,
157
- interrupted: true,
158
- stopped: false,
159
- staleInputs: false,
160
- },
161
- cell2: {
162
- runElapsedTimeMs: 456,
163
- status: "idle",
164
- errored: false,
165
- interrupted: false,
166
- stopped: false,
167
- staleInputs: false,
168
- },
169
- },
170
- } as any;
61
+ const state = notebook({ cell1: {}, cell2: {} });
62
+ const executions = MockExecutions.inputs([
63
+ MockExecutions.interrupted("cell1"),
64
+ MockExecutions.completed("cell2"),
65
+ ]);
66
+
67
+ expect(staleCellIds(state, executions)).toEqual(["cell1"]);
68
+ });
171
69
 
172
- const result = staleCellIds(state);
70
+ it("should return cell IDs with stale inputs", () => {
71
+ const state = notebook({ cell1: { staleInputs: true }, cell2: {} });
72
+ const executions = MockExecutions.inputs([
73
+ MockExecutions.completed("cell1"),
74
+ MockExecutions.completed("cell2"),
75
+ ]);
173
76
 
174
- expect(result).toEqual(["cell1"]);
77
+ expect(staleCellIds(state, executions)).toEqual(["cell1"]);
175
78
  });
176
79
 
177
80
  it("should not return cell IDs that are disabled", () => {
178
- const state: NotebookState = {
179
- cellIds: ["cell1", "cell2"],
180
- cellData: {
181
- cell1: {
182
- lastExecutionTime: 123,
183
- edited: false,
184
- config: { disabled: true },
185
- },
186
- cell2: {
187
- lastExecutionTime: 123,
188
- edited: false,
189
- config: { disabled: false },
190
- },
191
- },
192
- cellRuntime: {
193
- cell1: {
194
- runElapsedTimeMs: 456,
195
- status: "disabled-transitively",
196
- errored: false,
197
- interrupted: false,
198
- stopped: false,
199
- staleInputs: true,
200
- },
201
- cell2: {
202
- runElapsedTimeMs: 456,
203
- status: "idle",
204
- errored: false,
205
- interrupted: false,
206
- stopped: false,
207
- staleInputs: false,
208
- },
209
- },
210
- } as any;
211
-
212
- const result = staleCellIds(state);
81
+ const state = notebook({
82
+ cell1: { disabled: true, staleInputs: true },
83
+ cell2: { staleInputs: true },
84
+ });
85
+ const executions = MockExecutions.inputs(
86
+ [MockExecutions.completed("cell1"), MockExecutions.completed("cell2")],
87
+ { blocked: new Set(["cell2"]) },
88
+ );
213
89
 
214
- expect(result).toEqual([]);
90
+ expect(staleCellIds(state, executions)).toEqual([]);
215
91
  });
216
92
  });
217
93
 
218
94
  describe("isUninstantiated", () => {
219
- it("should return true if autoInstantiate is false and cell has not run", () => {
220
- const result = isUninstantiated({
221
- executionTime: null,
222
- status: "idle",
223
- errored: false,
224
- interrupted: false,
225
- stopped: false,
226
- });
227
- expect(result).toBe(true);
95
+ it("should return true if the cell has not run", () => {
96
+ expect(isUninstantiated({ status: "idle", outcome: null })).toBe(true);
228
97
  });
229
98
 
230
- it("should return false if cell has run", () => {
231
- const result = isUninstantiated({
232
- executionTime: 123,
233
- status: "idle",
234
- errored: false,
235
- interrupted: false,
236
- stopped: false,
237
- });
238
- expect(result).toBe(false);
99
+ it("should return false if the cell has run", () => {
100
+ expect(isUninstantiated({ status: "idle", outcome: "value" })).toBe(false);
239
101
  });
240
102
 
241
- it("should return false if cell is currently queued or running", () => {
242
- let result = isUninstantiated({
243
- executionTime: null,
244
- status: "queued",
245
- errored: false,
246
- interrupted: false,
247
- stopped: false,
248
- });
249
- expect(result).toBe(false);
250
-
251
- result = isUninstantiated({
252
- executionTime: null,
253
- status: "running",
254
- errored: false,
255
- interrupted: false,
256
- stopped: false,
257
- });
258
- expect(result).toBe(false);
103
+ it("should return false if the cell is currently queued or running", () => {
104
+ expect(isUninstantiated({ status: "queued", outcome: null })).toBe(false);
105
+ expect(isUninstantiated({ status: "running", outcome: null })).toBe(false);
259
106
  });
260
107
 
261
- it("should return false if cell is in an error state", () => {
262
- let result = isUninstantiated({
263
- executionTime: null,
264
- status: "idle",
265
- errored: true,
266
- interrupted: false,
267
- stopped: false,
268
- });
269
- expect(result).toBe(false);
270
-
271
- result = isUninstantiated({
272
- executionTime: null,
273
- status: "idle",
274
- errored: false,
275
- interrupted: true,
276
- stopped: false,
277
- });
278
- expect(result).toBe(false);
279
-
280
- result = isUninstantiated({
281
- executionTime: null,
282
- status: "idle",
283
- errored: false,
284
- interrupted: false,
285
- stopped: true,
286
- });
287
- expect(result).toBe(false);
108
+ it("should return false if the cell ended without a value", () => {
109
+ expect(isUninstantiated({ status: "idle", outcome: "error" })).toBe(false);
110
+ expect(isUninstantiated({ status: "idle", outcome: "interrupted" })).toBe(
111
+ false,
112
+ );
113
+ expect(isUninstantiated({ status: "idle", outcome: "skipped" })).toBe(
114
+ false,
115
+ );
288
116
  });
289
117
  });
290
118
 
@@ -6,6 +6,7 @@ import { historyField } from "@codemirror/commands";
6
6
  import { atom, type Getter, type Setter, useSetAtom } from "jotai";
7
7
  import { useMemo } from "react";
8
8
  import { scrollAndHighlightCell } from "@/components/editor/links/cell-link";
9
+ import { cellExecutionInputsAtom, runningCellIds } from "@/core/executions";
9
10
  import type { EditableDocument } from "@/core/notebook/document";
10
11
  import { Logger } from "@/utils/Logger";
11
12
  import { clamp } from "@/utils/math";
@@ -680,8 +681,10 @@ export type CellActions = ReturnType<typeof useCellActions>;
680
681
 
681
682
  /** Scroll to the first cell that is currently running. */
682
683
  export function notebookScrollToRunning() {
683
- const { cellIds, cellRuntime } = store.get(notebookAtom);
684
- const cellId = cellIds.find((id) => cellRuntime[id]?.status === "running");
684
+ const running = new Set(
685
+ runningCellIds(store.get(cellExecutionInputsAtom).executions),
686
+ );
687
+ const cellId = store.get(notebookAtom).cellIds.find((id) => running.has(id));
685
688
  if (!cellId) {
686
689
  return;
687
690
  }
@@ -1,11 +1,8 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- import { logNever } from "@/utils/assertNever";
3
- import { invariant } from "@/utils/invariant";
4
- import { type Seconds, Time } from "@/utils/time";
2
+ import type { CellExecutionView, CellRunStatus } from "@/core/executions";
5
3
  import { extractAllTracebackInfo, type TracebackInfo } from "@/utils/traceback";
6
4
  import { parseOutline } from "../dom/outline";
7
5
  import type { CellMessage, OutputMessage } from "../kernel/messages";
8
- import type { RuntimeState } from "../network/types";
9
6
  import { collapseConsoleOutputs } from "./collapseConsoleOutputs";
10
7
  import type { CellRuntimeState, ConsoleOutputMessage } from "./types";
11
8
 
@@ -14,111 +11,44 @@ function nextConsoleOutputId(outputs: ConsoleOutputMessage[]): number {
14
11
  return (outputs.at(-1)?.id ?? 0) + 1;
15
12
  }
16
13
 
14
+ function errorTypes(output: OutputMessage | null | undefined): string[] {
15
+ if (
16
+ output == null ||
17
+ output.mimetype !== "application/vnd.marimo+error" ||
18
+ !Array.isArray(output.data)
19
+ ) {
20
+ return [];
21
+ }
22
+ return output.data.map((error) => error.type);
23
+ }
24
+
17
25
  export function transitionCell(
18
26
  cell: CellRuntimeState,
19
27
  message: CellMessage,
20
28
  ): CellRuntimeState {
21
29
  const nextCell = { ...cell };
22
30
 
23
- // Handle status transition and update output; message.status !== null
24
- // implies a status transition
25
- switch (message.status) {
26
- case "queued":
27
- nextCell.interrupted = false;
28
- nextCell.errored = false;
29
- nextCell.runElapsedTimeMs = null;
30
- nextCell.debuggerActive = false;
31
- // We intentionally don't update lastCodeRun, since the kernel queues
32
- // whatever code was last registered with it, which might not match
33
- // the cell's current code if the user modified it.
34
- break;
35
- case "running":
36
- // Clear interrupted here in case we start as "running"
37
- // This can happen on a resumed session
38
- nextCell.interrupted = false;
39
- // If was previously stopped, clear the outputs
40
- if (cell.stopped) {
41
- nextCell.output = null;
42
- }
43
- // If it transitioned from queued to running, remove previous console outputs
44
- if (nextCell.status === "queued") {
45
- nextCell.consoleOutputs = [];
46
- }
47
- nextCell.stopped = false;
48
- nextCell.runStartTimestamp = message.timestamp as Seconds;
49
- // Store the last run timestamp, since this gets cleared once idle
50
- nextCell.lastRunStartTimestamp = message.timestamp as Seconds;
51
- break;
52
- case "idle":
53
- if (cell.runStartTimestamp) {
54
- nextCell.runElapsedTimeMs = Time.fromSeconds(
55
- ((message.timestamp ?? 0) - cell.runStartTimestamp) as Seconds,
56
- ).toMilliseconds();
57
- nextCell.runStartTimestamp = null;
58
- nextCell.staleInputs = false;
59
- }
60
- // If last run start timestamp is not set, set it to the current timestamp
61
- // This happens on a resumed session
62
- if (!cell.lastRunStartTimestamp && message.timestamp) {
63
- nextCell.lastRunStartTimestamp = message.timestamp as Seconds;
64
- }
65
- nextCell.debuggerActive = false;
66
- break;
67
- case null:
68
- break;
69
- case "disabled-transitively":
70
- // Everything should already be up to date from prepareCellForExecution
71
- break;
72
- case undefined:
73
- break;
74
- default:
75
- logNever(message.status);
31
+ if (message.status === "queued") {
32
+ nextCell.debuggerActive = false;
33
+ }
34
+ // A skipped cell's output is the notice that an ancestor stopped it, which
35
+ // has nothing to do with the run that is starting.
36
+ if (
37
+ message.status === "running" &&
38
+ errorTypes(cell.output).some((type) => type.includes("ancestor"))
39
+ ) {
40
+ nextCell.output = null;
76
41
  }
77
42
 
78
43
  nextCell.output = message.output ?? nextCell.output;
79
44
  nextCell.staleInputs = message.stale_inputs ?? nextCell.staleInputs;
80
- nextCell.status = message.status ?? nextCell.status;
81
45
  // Tri-state partial update: an omitted field (undefined) leaves the hint
82
46
  // unchanged; null explicitly clears it; a string sets it.
83
47
  if (message.serialization !== undefined) {
84
48
  nextCell.serialization = message.serialization;
85
49
  }
86
50
 
87
- let didInterruptFromThisMessage = false;
88
-
89
- // Handle errors: marimo includes an error output when a cell is interrupted
90
- // or errored
91
- if (
92
- message.output != null &&
93
- message.output.mimetype === "application/vnd.marimo+error"
94
- ) {
95
- // The frontend manually sets status to queued when a user runs a cell,
96
- // to give immediate feedback, but the kernel doesn't know that.
97
- //
98
- // TODO(akshayka): Move all status management to the backend.
99
- if (nextCell.status === "queued" || nextCell.status === "running") {
100
- nextCell.status = "idle";
101
- }
102
-
103
- invariant(
104
- Array.isArray(message.output.data),
105
- "Expected error output data to be an array",
106
- );
107
- if (message.output.data.some((error) => error.type === "interruption")) {
108
- // Interrupted helps distinguish that the cell is stale
109
- nextCell.interrupted = true;
110
- didInterruptFromThisMessage = true;
111
- } else if (
112
- message.output.data.some((error) => error.type.includes("ancestor"))
113
- ) {
114
- // The cell didn't run, but it was intentional, so don't count as
115
- // errored.
116
- nextCell.stopped = true;
117
- } else {
118
- // Communicate that the cell errored (e.g., an exception was raised)
119
- nextCell.errored = true;
120
- }
121
- }
51
+ const interrupted = errorTypes(message.output).includes("interruption");
122
52
 
123
53
  // Coalesce console outputs, which are streamed during execution.
124
54
  let consoleOutputs = cell.consoleOutputs;
@@ -127,7 +57,7 @@ export function transitionCell(
127
57
  // stdin/pdb session, so resolve any dangling stdin prompt. This removes the
128
58
  // debugger box — e.g. after quitting pdb with `q`, which stops the cell
129
59
  // (idle) rather than interrupting it.
130
- if (didInterruptFromThisMessage || message.status === "idle") {
60
+ if (interrupted || message.status === "idle") {
131
61
  nextCell.debuggerActive = false;
132
62
  consoleOutputs = consoleOutputs.map((output) => {
133
63
  if (output.channel === "stdin") {
@@ -177,30 +107,10 @@ export function transitionCell(
177
107
  return nextCell;
178
108
  }
179
109
 
180
- // Should be called when a cell's code is registered with the kernel for
181
- // execution.
182
- export function prepareCellForExecution(
183
- cell: CellRuntimeState,
184
- ): CellRuntimeState {
185
- const nextCell = { ...cell };
186
-
187
- if (cell.status !== "disabled-transitively") {
188
- // TODO(akshayka): Move this to the backend. It's in the FE right now
189
- // to give the user immediate feedback.
190
- nextCell.status = "queued";
191
- }
192
- nextCell.interrupted = false;
193
- nextCell.errored = false;
194
- nextCell.runElapsedTimeMs = null;
195
- nextCell.debuggerActive = false;
196
-
197
- return nextCell;
198
- }
199
-
200
110
  /**
201
111
  * A cell's output is loading if it is running or queued.
202
112
  */
203
- export function outputIsLoading(status: RuntimeState): boolean {
113
+ export function outputIsLoading(status: CellRunStatus): boolean {
204
114
  return status === "running" || status === "queued";
205
115
  }
206
116
 
@@ -208,16 +118,14 @@ export function outputIsLoading(status: RuntimeState): boolean {
208
118
  * A cell's output is stale if it has been edited, is loading, or has errored.
209
119
  */
210
120
  export function outputIsStale(
211
- cell: Pick<
212
- CellRuntimeState,
213
- "status" | "output" | "runStartTimestamp" | "interrupted" | "staleInputs"
214
- >,
121
+ execution: Pick<CellExecutionView, "status" | "outcome" | "rendered">,
122
+ staleInputs: boolean,
215
123
  edited: boolean,
216
124
  ): boolean {
217
- const { status, output, runStartTimestamp, interrupted, staleInputs } = cell;
125
+ const { status, outcome, rendered } = execution;
218
126
 
219
127
  // If interrupted, the output is not stale
220
- if (interrupted) {
128
+ if (outcome === "interrupted") {
221
129
  return false;
222
130
  }
223
131
 
@@ -226,18 +134,8 @@ export function outputIsStale(
226
134
  return true;
227
135
  }
228
136
 
229
- // The cell is loading
230
- const loading = outputIsLoading(status);
231
-
232
- // Output is received while the cell is running (e.g. mo.output.append())
233
- const outputReceivedWhileRunning =
234
- status === "running" &&
235
- output !== null &&
236
- runStartTimestamp !== null &&
237
- (output.timestamp ?? 0) > runStartTimestamp;
238
-
239
- // If loading and output has not been received while running
240
- if (loading && !outputReceivedWhileRunning) {
137
+ // Loading, unless the running cell has shown output (e.g. mo.output.append())
138
+ if (outputIsLoading(status) && !(status === "running" && rendered)) {
241
139
  return true;
242
140
  }
243
141