@marimo-team/islands 0.25.3-dev266 → 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 (184) hide show
  1. package/dist/{ConnectedDataExplorerComponent-C_hj7Poi.js → ConnectedDataExplorerComponent-eLHPh6uu.js} +9 -9
  2. package/dist/{ErrorBoundary-CvxlC3F1.js → ErrorBoundary-CQYAoD7X.js} +64 -69
  3. package/dist/{abnfDiagram-VCTEODGH-DzrZZXRD.js → abnfDiagram-VCTEODGH-hqd0IBtX.js} +2 -2
  4. package/dist/{any-language-editor-CFeWfC3o.js → any-language-editor-DnG26QXh.js} +1 -1
  5. package/dist/{architectureDiagram-5GKGNRK7-iF_snyFL.js → architectureDiagram-5GKGNRK7-BzLAFcc0.js} +1 -1
  6. package/dist/{strings-BCKpLFNU.js → assertNever-DIU3JJbt.js} +14 -14
  7. package/dist/{chat-ui-DFxNgzXF.js → chat-ui-BAnLgpTe.js} +1964 -1740
  8. package/dist/{chunk-IMKFNOWR-B7xytD4B.js → chunk-IMKFNOWR-DOeVvuyE.js} +1 -1
  9. package/dist/{chunk-SHT3W25Y-I8ZFP5L1.js → chunk-SHT3W25Y-DZoFI8wR.js} +1 -1
  10. package/dist/{chunk-SVP7TREG-Di-uAPdT.js → chunk-SVP7TREG-CWu86PSW.js} +1 -1
  11. package/dist/{chunk-TICWLB2K-BFpUy0ud.js → chunk-TICWLB2K-BrM0GoP3.js} +1 -1
  12. package/dist/{classDiagram-ZZMXUADV-Cqm4JGpx.js → classDiagram-ZZMXUADV-DYi4V2bB.js} +2 -2
  13. package/dist/{classDiagram-v2-VYDZK3BY-DFHRJpN3.js → classDiagram-v2-VYDZK3BY-tN7FIk-m.js} +2 -2
  14. package/dist/{code-visibility-SRM1JiD_.js → code-visibility-DcZ_RwaH.js} +44 -47
  15. package/dist/{common-yiRhZ6ZZ.js → common-BGeQilvh.js} +5846 -5519
  16. package/dist/{config-CmeG_Pb6.js → config-BsPruWMR.js} +2 -2
  17. package/dist/{createReducer-C_syF8Td.js → createReducer-DdtT_7ji.js} +1 -1
  18. package/dist/{cynefinDiagram-5FMLGOSQ-cuGOeTBC.js → cynefinDiagram-5FMLGOSQ-D5nCnDc-.js} +1 -1
  19. package/dist/{dagre-GXQ25YYZ-DJ76aC4k.js → dagre-GXQ25YYZ-BHkd7f_i.js} +1 -1
  20. package/dist/{data-grid-overlay-editor-DLTzcQ4O.js → data-grid-overlay-editor-Dc4lW4A3.js} +1 -1
  21. package/dist/{diagram-S7CK7UJ4-DLLc0sw7.js → diagram-S7CK7UJ4-D1ALppT2.js} +1 -1
  22. package/dist/{diagram-UQ7AKVKN-Tz_d6qmk.js → diagram-UQ7AKVKN-DLv0b83L.js} +1 -1
  23. package/dist/{diagram-VX7I27RA-D7SzPrDN.js → diagram-VX7I27RA-7XJ7J6qg.js} +1 -1
  24. package/dist/{diagram-Z3DM3KII-qr37XV58.js → diagram-Z3DM3KII-7M7zbHp_.js} +1 -1
  25. package/dist/{dialog-BtprQCUw.js → dialog-DzCj1qQv.js} +1 -1
  26. package/dist/{dropdown-menu-CkJ7ErY6.js → dropdown-menu-CdXE15an.js} +1214 -1208
  27. package/dist/{ebnfDiagram-PWID7BFC-BKOQb4J0.js → ebnfDiagram-PWID7BFC-DHH1qlmV.js} +2 -2
  28. package/dist/{erDiagram-RLTQ6QDP-CM15Vq68.js → erDiagram-RLTQ6QDP-fI_0dGef.js} +1 -1
  29. package/dist/{error-banner-DbRFOd2O.js → error-banner-DPc9eIw5.js} +3 -3
  30. package/dist/{error-entries-CMoaUX9V.js → error-entries-CxN-Nq3V.js} +524 -456
  31. package/dist/{feedback-button-DBUrE4OL.js → feedback-button-BqaeQcvX.js} +77 -77
  32. package/dist/{flowDiagram-HODETNUW-Dt3N5yFT.js → flowDiagram-HODETNUW-Brg9MA6i.js} +2 -2
  33. package/dist/{formats-gGospKWJ.js → formats-DbSvAPY-.js} +1 -1
  34. package/dist/{glide-data-editor-Cfv4HeOh.js → glide-data-editor-CiijPXA7.js} +2042 -2041
  35. package/dist/{glide-utils-Ba9WD2IN.js → glide-utils-BSw3SSHF.js} +29 -35
  36. package/dist/{globals-dIbBIR5-.js → globals-CpmSWKI_.js} +2 -2
  37. package/dist/{html-to-image-D_5CMreP.js → html-to-image-8sywF4my.js} +5452 -5751
  38. package/dist/{infoDiagram-27XIBGKW-C7axJMCl.js → infoDiagram-27XIBGKW-E9e403YT.js} +1 -1
  39. package/dist/{ishikawaDiagram-5VMMS53U-CdC9Eur7.js → ishikawaDiagram-5VMMS53U-C-RlYDRo.js} +1 -1
  40. package/dist/{kanban-definition-UXKFOSKX-BHnMIgD7.js → kanban-definition-UXKFOSKX-CLFl_uqK.js} +1 -1
  41. package/dist/main.js +6294 -6324
  42. package/dist/{mermaid-DFYN_PXd.js → mermaid-rd09e1JL.js} +35 -35
  43. package/dist/{mindmap-definition-YA3MSWOX-BagHoCGU.js → mindmap-definition-YA3MSWOX-BQ8TWtyB.js} +1 -1
  44. package/dist/{number-overlay-editor-CTiJp5Mj.js → number-overlay-editor-Ft4NiAEN.js} +1 -1
  45. package/dist/{pegDiagram-XKGWAZYB-8IIZczip.js → pegDiagram-XKGWAZYB-CN8UHFPi.js} +2 -2
  46. package/dist/pencil-DcT8AE0h.js +10 -0
  47. package/dist/{pieDiagram-E7YTZNPT-CE6dKBIr.js → pieDiagram-E7YTZNPT-DGuJ0EBd.js} +1 -1
  48. package/dist/{process-output-CGFNVC60.js → process-output-Dku5POvm.js} +3 -3
  49. package/dist/{railroadDiagram-O6MQD6OU-CkexPq3b.js → railroadDiagram-O6MQD6OU-BPDA8wy8.js} +2 -2
  50. package/dist/{requirementDiagram-BXWQKSXE-B0-OkuAe.js → requirementDiagram-BXWQKSXE-ChzLGbGW.js} +1 -1
  51. package/dist/{reveal-component-DH09ygHo.js → reveal-component-D0cfwbbl.js} +1094 -1094
  52. package/dist/{select-C33FVPPN.js → select-OxgozOC8.js} +1 -1
  53. package/dist/{sequenceDiagram-WJ2MYXX4-Dxysg8DX.js → sequenceDiagram-WJ2MYXX4-DFX-bE6O.js} +1 -1
  54. package/dist/{spec-DH1lmF7Q.js → spec-oJn0RhpQ.js} +2 -2
  55. package/dist/{stateDiagram-D77RDMKH-DTEN3-OJ.js → stateDiagram-D77RDMKH-BR7cI3wK.js} +2 -2
  56. package/dist/{stateDiagram-v2-MP3YSRHH-vktk3Xc5.js → stateDiagram-v2-MP3YSRHH-oGqB5UDI.js} +2 -2
  57. package/dist/{static-state-BldGd99T.js → static-state-CuV6Pc3a.js} +1 -1
  58. package/dist/style.css +1 -1
  59. package/dist/{swimlanes-42K2YHIH-Cjveh9OM.js → swimlanes-42K2YHIH-DwSZl1yC.js} +1 -1
  60. package/dist/{swimlanesDiagram-VR7AAH4N-Bdr7Ev_2.js → swimlanesDiagram-VR7AAH4N-BHlcd-Ri.js} +2 -2
  61. package/dist/{timeline-definition-24CTP7MA-KIuDcefn.js → timeline-definition-24CTP7MA-DMmP-G2O.js} +1 -1
  62. package/dist/{toast-CY1_Qv41.js → toast-DbBHmxmB.js} +2 -2
  63. package/dist/{useAsyncData-DsZMvNXn.js → useAsyncData-BYIAoMqn.js} +1 -1
  64. package/dist/{useDateFormatter-Dk_xFci4.js → useDateFormatter-ButWkdcK.js} +1 -1
  65. package/dist/{useDeepCompareMemoize-KMBBux8C.js → useDeepCompareMemoize-Bz0c5GN6.js} +1 -1
  66. package/dist/{useTheme-BZ2GGLgm.js → useTheme-DPLobSFs.js} +7 -7
  67. package/dist/{utils-DXkj82sK.js → utils-EBklsC5R.js} +31 -31
  68. package/dist/{vega-component-C-lqjV0q.js → vega-component-rxIxAAh9.js} +7 -7
  69. package/dist/{vennDiagram-4TSXK5OY-DrIOUHfb.js → vennDiagram-4TSXK5OY-Db8GtH1G.js} +1 -1
  70. package/dist/{wardleyDiagram-VM6X3IG4-CJNjf4OH.js → wardleyDiagram-VM6X3IG4-DmIiC3FU.js} +1 -1
  71. package/dist/{xychartDiagram-S5SC5T6Z-DEshFho-.js → xychartDiagram-S5SC5T6Z-DS9OPrNq.js} +1 -1
  72. package/package.json +1 -1
  73. package/src/__mocks__/executions.ts +195 -0
  74. package/src/__mocks__/notebook.ts +0 -1
  75. package/src/__tests__/CellStatus.test.tsx +19 -20
  76. package/src/components/chat/__tests__/chat-display.test.tsx +157 -86
  77. package/src/components/chat/__tests__/chat-utils.test.ts +42 -0
  78. package/src/components/chat/__tests__/turn-timing.test.ts +84 -0
  79. package/src/components/chat/__tests__/use-assistant-turn.test.ts +95 -0
  80. package/src/components/chat/__tests__/user-message.test.tsx +27 -0
  81. package/src/components/chat/acp/agent-panel.tsx +2 -0
  82. package/src/components/chat/agent-activity.tsx +132 -0
  83. package/src/components/chat/chat-display.tsx +125 -96
  84. package/src/components/chat/chat-panel.tsx +123 -106
  85. package/src/components/chat/chat-utils.ts +12 -24
  86. package/src/components/chat/tool-call/__tests__/tool-labels.test.ts +76 -0
  87. package/src/components/chat/tool-call/__tests__/tool-step.test.tsx +57 -0
  88. package/src/components/chat/tool-call/shared.ts +6 -0
  89. package/src/components/chat/tool-call/tool-call-view.tsx +22 -76
  90. package/src/components/chat/tool-call/tool-labels.ts +172 -0
  91. package/src/components/chat/tool-call/tool-step.tsx +145 -0
  92. package/src/components/chat/turn-timing.ts +73 -0
  93. package/src/components/chat/use-assistant-turn.ts +65 -0
  94. package/src/components/chat/use-chat-generation.ts +5 -4
  95. package/src/components/chat/user-message.tsx +145 -0
  96. package/src/components/dependency-graph/minimap-content.tsx +3 -2
  97. package/src/components/editor/__tests__/data-attributes.test.tsx +0 -7
  98. package/src/components/editor/__tests__/output-persistence.test.tsx +2 -2
  99. package/src/components/editor/actions/__tests__/useCellActionButton.test.tsx +19 -1
  100. package/src/components/editor/actions/useCellActionButton.tsx +10 -3
  101. package/src/components/editor/ai/completion-handlers.tsx +0 -37
  102. package/src/components/editor/alerts/stdin-blocking-alert.tsx +3 -1
  103. package/src/components/editor/cell/CellStatus.tsx +23 -19
  104. package/src/components/editor/cell/RunButton.tsx +16 -6
  105. package/src/components/editor/cell/StagedAICell.tsx +51 -144
  106. package/src/components/editor/cell/__tests__/useRunCells.test.ts +45 -0
  107. package/src/components/editor/cell/cell-context-menu.tsx +4 -1
  108. package/src/components/editor/cell/code/cell-editor.tsx +7 -5
  109. package/src/components/editor/cell/collapse.tsx +4 -0
  110. package/src/components/editor/cell/useRunCells.ts +18 -3
  111. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +193 -32
  112. package/src/components/editor/chrome/wrapper/app-chrome.tsx +2 -2
  113. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +145 -81
  114. package/src/components/editor/chrome/wrapper/pending-ai-changes-badge.tsx +25 -0
  115. package/src/components/editor/chrome/wrapper/sidebar.tsx +8 -4
  116. package/src/components/editor/header/status.tsx +4 -2
  117. package/src/components/editor/links/cell-link.tsx +4 -4
  118. package/src/components/editor/navigation/navigation.ts +3 -2
  119. package/src/components/editor/notebook-cell.tsx +85 -94
  120. package/src/components/editor/renderers/grid-layout/__tests__/grid-layout.test.tsx +75 -0
  121. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +22 -16
  122. package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +0 -1
  123. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +12 -33
  124. package/src/components/editor/stdin-blocking-alert.tsx +3 -1
  125. package/src/components/scratchpad/scratchpad.tsx +4 -1
  126. package/src/components/slides/minimap.tsx +4 -2
  127. package/src/components/slides/reveal-component.tsx +14 -10
  128. package/src/components/slides/slide-cell-view.tsx +28 -24
  129. package/src/components/slides/slide.tsx +3 -4
  130. package/src/components/ui/toast.tsx +1 -1
  131. package/src/core/ai/__tests__/staged-cells.test.ts +74 -126
  132. package/src/core/ai/staged-cells.ts +48 -38
  133. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +0 -389
  134. package/src/core/cells/__tests__/cell.test.ts +49 -154
  135. package/src/core/cells/__tests__/cells.test.ts +18 -127
  136. package/src/core/cells/__tests__/pending-delete-service.test.tsx +11 -20
  137. package/src/core/cells/__tests__/utils.test.ts +75 -247
  138. package/src/core/cells/actions.ts +5 -2
  139. package/src/core/cells/cell.ts +31 -133
  140. package/src/core/cells/cells.ts +14 -29
  141. package/src/core/cells/document.ts +3 -3
  142. package/src/core/cells/focus.ts +27 -6
  143. package/src/core/cells/layout.ts +1 -1
  144. package/src/core/cells/pending-delete-service.ts +5 -7
  145. package/src/core/cells/scrollCellIntoView.ts +6 -0
  146. package/src/core/cells/types.ts +13 -36
  147. package/src/core/cells/utils.ts +37 -68
  148. package/src/core/codemirror/placeholder/__tests__/extensions.test.ts +59 -0
  149. package/src/core/codemirror/placeholder/extensions.ts +23 -0
  150. package/src/core/edit-app.tsx +2 -1
  151. package/src/core/executions/__tests__/atoms.test.ts +99 -0
  152. package/src/core/executions/__tests__/select.test.ts +268 -0
  153. package/src/core/executions/atoms.ts +206 -0
  154. package/src/core/executions/hooks.ts +31 -0
  155. package/src/core/executions/index.ts +31 -0
  156. package/src/core/executions/select.ts +175 -0
  157. package/src/core/export/__tests__/hooks.test.ts +0 -5
  158. package/src/core/islands/bootstrap.ts +4 -1
  159. package/src/core/islands/components/output-wrapper.tsx +5 -5
  160. package/src/core/kernel/handlers.ts +2 -11
  161. package/src/core/kernel/state.ts +0 -15
  162. package/src/core/lifecycle/render-policy.ts +12 -26
  163. package/src/core/network/__tests__/requests-lazy.test.ts +5 -5
  164. package/src/core/network/requests-lazy.ts +3 -3
  165. package/src/core/notebook/operations.ts +1 -1
  166. package/src/core/run-app.tsx +2 -1
  167. package/src/core/runtime/__tests__/adapter.test.ts +3 -3
  168. package/src/core/runtime/adapter.ts +4 -4
  169. package/src/core/session/__tests__/useNotebookSession.hook.test.tsx +34 -0
  170. package/src/core/session/useNotebookSession.tsx +10 -5
  171. package/src/core/wasm/bridge.ts +1 -1
  172. package/src/css/app/Cell.css +7 -36
  173. package/src/plugins/core/registerReactComponent.tsx +4 -13
  174. package/src/plugins/impl/chat/chat-ui.tsx +2 -3
  175. package/src/plugins/impl/common/error-banner.tsx +5 -3
  176. package/src/stories/cell-status.stories.tsx +17 -10
  177. package/src/stories/cell.stories.tsx +0 -31
  178. package/src/stories/layout/vertical/one-column.stories.tsx +0 -9
  179. package/src/utils/time.ts +10 -0
  180. package/src/components/chat/reasoning-accordion.tsx +0 -61
  181. package/src/components/chat/tool-call/tool-activity-group.tsx +0 -49
  182. package/src/components/chat/tool-call/tool-error-card.tsx +0 -81
  183. package/src/components/chat/tool-call/tool-history-row.tsx +0 -147
  184. package/src/components/icons/loading-ellipsis.tsx +0 -46
@@ -6,6 +6,11 @@ import { atomFamily, selectAtom, splitAtom } from "jotai/utils";
6
6
  import { createRef, type ReducerWithoutAction } from "react";
7
7
  import type { CellHandle } from "@/components/editor/notebook-cell";
8
8
  import type { EditableDocument, Placement } from "@/core/notebook/document";
9
+ import {
10
+ cellExecutionInputsAtom,
11
+ cellExecutionView,
12
+ withdrawCells,
13
+ } from "@/core/executions";
9
14
  import type { CellColumnId } from "./layout";
10
15
  import { invariant } from "@/utils/invariant";
11
16
  import { Logger } from "@/utils/Logger";
@@ -21,7 +26,7 @@ import { isErrorMime } from "../mime";
21
26
  import type { CellConfig } from "../network/types";
22
27
  import { createDeepEqualAtom, store } from "../state/jotai";
23
28
  import { isWasm } from "../wasm/utils";
24
- import { prepareCellForExecution, transitionCell } from "./cell";
29
+ import { transitionCell } from "./cell";
25
30
  import { applyDocument } from "./document";
26
31
  import { isCodeHidden, shownHiddenCodeAtom } from "./editor";
27
32
  import { type CellId, SCRATCH_CELL_ID } from "./ids";
@@ -40,9 +45,7 @@ import {
40
45
  disabledCellIds,
41
46
  enabledCellIds,
42
47
  getUndoLabel,
43
- notebookIsRunning,
44
48
  notebookNeedsRun,
45
- notebookQueueOrRunningCount,
46
49
  } from "./utils";
47
50
 
48
51
  /** A deleted cell as it was, with where it went in the document. */
@@ -190,9 +193,7 @@ const {
190
193
  const newState = updateCellRuntimeState({
191
194
  state,
192
195
  cellId: action.cellId,
193
- cellReducer: (cell) => {
194
- return prepareCellForExecution(cell);
195
- },
196
+ cellReducer: (cell) => ({ ...cell, debuggerActive: false }),
196
197
  });
197
198
  return updateCellData({
198
199
  state: newState,
@@ -286,13 +287,7 @@ const {
286
287
  });
287
288
  },
288
289
  /** Record what the kernel last ran for each cell. */
289
- setLastRun: (
290
- state,
291
- action: {
292
- lastCodeRun: Record<CellId, string>;
293
- lastExecutionTime: Record<CellId, number>;
294
- },
295
- ) => {
290
+ setLastRun: (state, action: { lastCodeRun: Record<CellId, string> }) => {
296
291
  const cellData = { ...state.cellData };
297
292
  for (const cellId of state.cellIds) {
298
293
  const cell = state.cellData[cellId];
@@ -300,7 +295,6 @@ const {
300
295
  cellData[cellId] = {
301
296
  ...cell,
302
297
  lastCodeRun,
303
- lastExecutionTime: action.lastExecutionTime[cellId] ?? null,
304
298
  // A cell the kernel never ran is not stale on load.
305
299
  edited: lastCodeRun !== null && isEdited(lastCodeRun, cell.code),
306
300
  };
@@ -497,7 +491,9 @@ export const canUndoDeletesAtom = atom((get) =>
497
491
 
498
492
  export const undoLabelAtom = atom((get) => getUndoLabel(get(notebookAtom)));
499
493
 
500
- export const needsRunAtom = atom((get) => notebookNeedsRun(get(notebookAtom)));
494
+ export const needsRunAtom = atom((get) =>
495
+ notebookNeedsRun(get(notebookAtom), get(cellExecutionInputsAtom)),
496
+ );
501
497
 
502
498
  export const cellErrorsAtom = atom((get) => {
503
499
  const { cellIds, cellRuntime, cellData } = get(notebookAtom);
@@ -629,19 +625,6 @@ export const useCellDataAtoms = () => useAtom(cellDataAtoms);
629
625
 
630
626
  export const cellsRuntimeAtom = atom((get) => get(notebookAtom).cellRuntime);
631
627
 
632
- export const notebookIsRunningAtom = atom((get) =>
633
- notebookIsRunning(get(notebookAtom)),
634
- );
635
- export const onlyScratchpadIsRunningAtom = atom((get) => {
636
- const { cellRuntime } = get(notebookAtom);
637
- return Object.entries(cellRuntime).every(
638
- ([id, rt]) => rt.status !== "running" || id === SCRATCH_CELL_ID,
639
- );
640
- });
641
- export const notebookQueuedOrRunningCountAtom = atom((get) =>
642
- notebookQueueOrRunningCount(get(notebookAtom)),
643
- );
644
-
645
628
  export const hasCellsAtom = atom((get) => get(notebookAtom).cellIds.length > 0);
646
629
  export const cellDataAtom = atomFamily((cellId: CellId) =>
647
630
  atom((get) => get(notebookAtom).cellData[cellId]),
@@ -666,6 +649,7 @@ export function releaseCellAtoms(cellId: CellId) {
666
649
  cellDataAtom.remove(cellId);
667
650
  cellRuntimeAtom.remove(cellId);
668
651
  cellHandleAtom.remove(cellId);
652
+ withdrawCells(store, [cellId]);
669
653
  }
670
654
 
671
655
  /** Subscribes to reactive updates of the cell's data. */
@@ -726,7 +710,8 @@ export function createTracebackInfoAtom(
726
710
  return undefined;
727
711
  }
728
712
 
729
- if (data.status === "queued" || data.status === "running") {
713
+ const { status } = cellExecutionView(get(cellExecutionInputsAtom), cellId);
714
+ if (status === "queued" || status === "running") {
730
715
  return undefined;
731
716
  }
732
717
 
@@ -5,7 +5,7 @@
5
5
  * both. Edits go the other way through the document model directly: the
6
6
  * cell operations write it, and each cell editor is bound to its cell.
7
7
  *
8
- * TODO(MO-8028): Delete this module once components read cells from the
8
+ * TODO(MO-8349): Delete this module once components read cells from the
9
9
  * document model and `notebookAtom` holds only runtime and editor state.
10
10
  */
11
11
 
@@ -64,7 +64,7 @@ function followCell(current: CellData, cell: EditableCell): CellData {
64
64
  * Returns the same state when nothing differs, so a local edit that the
65
65
  * document already reflects changes nothing.
66
66
  *
67
- * TODO(MO-8028): Delete with `reconcileDocument` once cells are read from
67
+ * TODO(MO-8349): Delete with `reconcileDocument` once cells are read from
68
68
  * the document model.
69
69
  */
70
70
  export function applyDocument(
@@ -134,7 +134,7 @@ export function applyDocument(
134
134
  * Reconcile the model's document and name its columns, now and on each
135
135
  * change, until the signal aborts.
136
136
  *
137
- * TODO(MO-8028): Delete with `applyDocument`.
137
+ * TODO(MO-8349): Delete with `applyDocument`.
138
138
  */
139
139
  export function followDocument({
140
140
  store,
@@ -3,7 +3,13 @@
3
3
  import type { EditorView } from "@codemirror/view";
4
4
  import { atom, useAtomValue } from "jotai";
5
5
  import { createReducerAndAtoms } from "@/utils/createReducer";
6
- import type { CellConfig, RuntimeState } from "../network/types";
6
+ import {
7
+ type CellExecutionInputs,
8
+ type CellRunStatus,
9
+ cellExecutionInputsAtom,
10
+ cellExecutionView,
11
+ } from "@/core/executions";
12
+ import type { CellConfig } from "../network/types";
7
13
  import { type NotebookState, notebookAtom } from "./cells";
8
14
  import type { CellId } from "./ids";
9
15
  import { SCRATCH_CELL_ID } from "./ids";
@@ -75,7 +81,8 @@ export const lastFocusedCellAtom = atom<{
75
81
  name: string;
76
82
  config: CellConfig;
77
83
  cellId: CellId;
78
- status: RuntimeState;
84
+ status: CellRunStatus;
85
+ blocked: boolean;
79
86
  getEditorView: () => EditorView | null;
80
87
  hasOutput: boolean;
81
88
  hasConsoleOutput: boolean;
@@ -89,16 +96,28 @@ export const lastFocusedCellAtom = atom<{
89
96
  return null;
90
97
  }
91
98
 
92
- return cellFocusDetails(cellId, get(notebookAtom));
99
+ return cellFocusDetails(
100
+ cellId,
101
+ get(notebookAtom),
102
+ get(cellExecutionInputsAtom),
103
+ );
93
104
  });
94
105
 
95
106
  export function cellFocusDetailsAtom(cellId: CellId) {
96
107
  return atom((get) => {
97
- return cellFocusDetails(cellId, get(notebookAtom));
108
+ return cellFocusDetails(
109
+ cellId,
110
+ get(notebookAtom),
111
+ get(cellExecutionInputsAtom),
112
+ );
98
113
  });
99
114
  }
100
115
 
101
- function cellFocusDetails(cellId: CellId, notebookState: NotebookState) {
116
+ function cellFocusDetails(
117
+ cellId: CellId,
118
+ notebookState: NotebookState,
119
+ executions: CellExecutionInputs,
120
+ ) {
102
121
  const { cellData, cellHandles, cellRuntime } = notebookState;
103
122
  const data = cellData[cellId];
104
123
  const runtime = cellRuntime[cellId];
@@ -107,12 +126,14 @@ function cellFocusDetails(cellId: CellId, notebookState: NotebookState) {
107
126
  return null;
108
127
  }
109
128
  const getEditorView = () => handle.editorViewOrNull;
129
+ const execution = cellExecutionView(executions, cellId);
110
130
 
111
131
  return {
112
132
  cellId,
113
133
  name: data.name,
114
134
  config: data.config,
115
- status: runtime ? runtime.status : "idle",
135
+ status: execution.status,
136
+ blocked: execution.blocked,
116
137
  getEditorView: getEditorView,
117
138
  hasOutput: runtime?.output != null,
118
139
  hasConsoleOutput: runtime?.consoleOutputs != null,
@@ -140,7 +140,7 @@ export function deriveLayout(
140
140
  * The layout of the notebook's cells: the document's order and columns,
141
141
  * this browser's column ids and folds, and the kernel's outlines.
142
142
  *
143
- * TODO(MO-8028): Outlines come from executions later.
143
+ * TODO(MO-8404): Outlines come from the host.
144
144
  */
145
145
  export const layoutAtom = atom((get): Layout => {
146
146
  const model = get(activeDocumentModelAtom);
@@ -7,6 +7,7 @@ import {
7
7
  } from "@/components/editor/cell/useDeleteCell";
8
8
  import { notebookAtom } from "@/core/cells/cells";
9
9
  import type { CellId } from "@/core/cells/ids";
10
+ import { cellExecutionInputsAtom, cellExecutionView } from "@/core/executions";
10
11
  import type { JotaiStore } from "@/core/state/jotai";
11
12
  import { variablesAtom } from "@/core/variables/state";
12
13
  import type { VariableName } from "@/core/variables/types";
@@ -46,6 +47,7 @@ const { valueAtom: pendingDeleteStateAtom, useActions } = createReducerAndAtoms(
46
47
  ) => {
47
48
  const { cellIds, deleteCell, deleteManyCells, store } = action;
48
49
  const notebook = store.get(notebookAtom);
50
+ const executions = store.get(cellExecutionInputsAtom);
49
51
  const variables = store.get(variablesAtom);
50
52
 
51
53
  const emptyCells = new Set(
@@ -62,8 +64,6 @@ const { valueAtom: pendingDeleteStateAtom, useActions } = createReducerAndAtoms(
62
64
  continue;
63
65
  }
64
66
 
65
- const runtimeInfo = notebook.cellRuntime[cellId];
66
-
67
67
  // Build defs map for this cell
68
68
  const defs = new Map<VariableName, readonly CellId[]>();
69
69
  for (const variable of Object.values(variables)) {
@@ -77,11 +77,9 @@ const { valueAtom: pendingDeleteStateAtom, useActions } = createReducerAndAtoms(
77
77
  }
78
78
 
79
79
  let executionDurationMs: number | undefined;
80
- if (
81
- runtimeInfo?.runElapsedTimeMs &&
82
- runtimeInfo.runElapsedTimeMs > 2000
83
- ) {
84
- executionDurationMs = runtimeInfo.runElapsedTimeMs;
80
+ const { duration } = cellExecutionView(executions, cellId);
81
+ if (duration !== null && duration > 2000) {
82
+ executionDurationMs = duration;
85
83
  }
86
84
 
87
85
  if (defs.size === 0 && executionDurationMs === undefined) {
@@ -160,3 +160,9 @@ export function scrollCellIntoView(cellId: CellId): void {
160
160
  );
161
161
  }
162
162
  }
163
+
164
+ export function scrollToCell(cellId: CellId): HTMLElement | null {
165
+ const cell = document.getElementById(HTMLCellId.create(cellId));
166
+ cell?.scrollIntoView({ behavior: "smooth", block: "center" });
167
+ return cell;
168
+ }
@@ -1,8 +1,7 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import type { Milliseconds, Seconds } from "@/utils/time";
4
3
  import type { OutputMessage } from "../kernel/messages";
5
- import type { CellConfig, RuntimeState } from "../network/types";
4
+ import type { CellConfig } from "../network/types";
6
5
  import type { CellId } from "./ids";
7
6
  import { DEFAULT_CELL_NAME } from "./names";
8
7
  import type { Outline } from "./outline";
@@ -11,7 +10,7 @@ import type { Outline } from "./outline";
11
10
  * Whether a cell's code has changed since it last ran. A cell that never ran
12
11
  * is edited once it has code. Whitespace at the ends does not count.
13
12
  *
14
- * TODO(MO-8028): Temporary until staleness comes from the host's executions.
13
+ * TODO(MO-8403): Delete with `lastCodeRun`.
15
14
  */
16
15
  export function isEdited(lastCodeRun: string | null, code: string): boolean {
17
16
  return lastCodeRun === null
@@ -27,7 +26,6 @@ export function createCell({
27
26
  name = DEFAULT_CELL_NAME,
28
27
  code = "",
29
28
  lastCodeRun = null,
30
- lastExecutionTime = null,
31
29
  edited = false,
32
30
  config,
33
31
  }: Partial<CellData> & { id: CellId }): CellData {
@@ -43,7 +41,6 @@ export function createCell({
43
41
  code: code,
44
42
  edited: edited,
45
43
  lastCodeRun: lastCodeRun,
46
- lastExecutionTime: lastExecutionTime,
47
44
  };
48
45
  }
49
46
 
@@ -64,14 +61,7 @@ export function createCellRuntimeState(
64
61
  outline: null,
65
62
  output: null,
66
63
  consoleOutputs: [],
67
- status: "idle" as RuntimeState,
68
64
  staleInputs: false,
69
- interrupted: false,
70
- errored: false,
71
- stopped: false,
72
- runElapsedTimeMs: null,
73
- runStartTimestamp: null,
74
- lastRunStartTimestamp: null,
75
65
  debuggerActive: false,
76
66
  ...state,
77
67
  };
@@ -90,41 +80,28 @@ export interface CellData {
90
80
  edited: boolean;
91
81
  /** snapshot of code that was last run */
92
82
  lastCodeRun: string | null;
93
- /** execution time on session start / resume */
94
- lastExecutionTime: number | null;
95
83
  /** cell configuration */
96
84
  config: CellConfig;
97
85
  }
98
86
 
87
+ /**
88
+ * What the kernel's cell-ops still own. Status, timing, and how a cell last
89
+ * ended come from the host's executions (see `@/core/executions`).
90
+ *
91
+ * TODO(MO-8407): Delete.
92
+ */
99
93
  export interface CellRuntimeState {
100
- /** a message encoding the cell's output */
94
+ /** a message encoding the cell's output. TODO(MO-8404) */
101
95
  output: OutputMessage | null;
102
- /** TOC outline */
96
+ /** TOC outline. TODO(MO-8404) */
103
97
  outline: Outline | null;
104
- /** messages encoding the cell's console outputs. */
98
+ /** messages encoding the cell's console outputs. TODO(MO-8405) */
105
99
  consoleOutputs: ConsoleOutputMessage[];
106
- /** current status of the cell */
107
- status: RuntimeState;
108
- /** whether the cell has stale inputs*/
100
+ /** whether the cell has stale inputs. TODO(MO-8403) */
109
101
  staleInputs: boolean;
110
- /** whether this cell has been interrupted since its last run */
111
- interrupted: boolean;
112
- /** whether this cell was stopped with mo.stop */
113
- stopped: boolean;
114
- /**
115
- * whether marimo encountered an error when trying to register or run
116
- * this cell (such as a multiple definition error)
117
- */
118
- errored: boolean;
119
- /** run start time, as seconds since epoch */
120
- runStartTimestamp: Seconds | null;
121
- /** run elapsed time, in milliseconds */
122
- runElapsedTimeMs: Milliseconds | null;
123
- /** last run timestamp, as seconds since epoch */
124
- lastRunStartTimestamp: Seconds | null;
125
102
  /** debugger active */
126
103
  debuggerActive: boolean;
127
- /** serialization message */
104
+ /** serialization message. TODO(MO-8414) */
128
105
  serialization?: string | null;
129
106
  }
130
107
 
@@ -1,27 +1,22 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import type { EditorView } from "@codemirror/view";
4
+ import {
5
+ type CellExecutionInputs,
6
+ type CellExecutionView,
7
+ cellExecutionView,
8
+ } from "@/core/executions";
4
9
  import { Objects } from "@/utils/objects";
5
10
  import type { MarimoError, OutputMessage } from "../kernel/messages";
6
11
  import { isErrorMime } from "../mime";
7
- import type { RuntimeState } from "../network/types";
8
12
  import type { NotebookState } from "./cells";
9
13
  import type { CellId } from "./ids";
10
14
 
11
- export function notebookIsRunning(state: NotebookState) {
12
- return Object.values(state.cellRuntime).some(
13
- (cell) => cell.status === "running",
14
- );
15
- }
16
-
17
- export function notebookQueueOrRunningCount(state: NotebookState) {
18
- return Object.values(state.cellRuntime).filter(
19
- (cell) => cell.status === "running" || cell.status === "queued",
20
- ).length;
21
- }
22
-
23
- export function notebookNeedsRun(state: NotebookState) {
24
- return staleCellIds(state).length > 0;
15
+ export function notebookNeedsRun(
16
+ state: NotebookState,
17
+ executions: CellExecutionInputs,
18
+ ) {
19
+ return staleCellIds(state, executions).length > 0;
25
20
  }
26
21
 
27
22
  export function notebookCells(state: NotebookState) {
@@ -84,16 +79,16 @@ export function getUndoLabel(state: NotebookState): string {
84
79
  */
85
80
  export function getDescendantsStatus(
86
81
  state: NotebookState,
82
+ executions: CellExecutionInputs,
87
83
  descendants: readonly CellId[],
88
84
  ) {
85
+ const views = descendants.map((id) => cellExecutionView(executions, id));
89
86
  const stale = descendants.some(
90
87
  (id) => state.cellRuntime[id]?.staleInputs || state.cellData[id]?.edited,
91
88
  );
92
- const errored = descendants.some((id) => state.cellRuntime[id]?.errored);
93
- const runningOrQueued = descendants.some(
94
- (id) =>
95
- state.cellRuntime[id]?.status === "running" ||
96
- state.cellRuntime[id]?.status === "queued",
89
+ const errored = views.some((view) => view.outcome === "error");
90
+ const runningOrQueued = views.some(
91
+ (view) => view.status === "running" || view.status === "queued",
97
92
  );
98
93
 
99
94
  return {
@@ -106,54 +101,30 @@ export function getDescendantsStatus(
106
101
  /**
107
102
  * Cells that are stale and can be run.
108
103
  */
109
- export function staleCellIds(state: NotebookState) {
104
+ export function staleCellIds(
105
+ state: NotebookState,
106
+ executions: CellExecutionInputs,
107
+ ) {
110
108
  const { cellIds, cellData, cellRuntime } = state;
111
- return cellIds.filter(
112
- (cellId) =>
113
- isUninstantiated({
114
- // runElapstedTimeMs is what we've seen in this session
115
- executionTime:
116
- cellRuntime[cellId].runElapsedTimeMs ??
117
- // lastExecutionTime is what was seen on session start/resume
118
- cellData[cellId].lastExecutionTime,
119
- status: cellRuntime[cellId].status,
120
- errored: cellRuntime[cellId].errored,
121
- interrupted: cellRuntime[cellId].interrupted,
122
- stopped: cellRuntime[cellId].stopped,
123
- }) ||
109
+ return cellIds.filter((cellId) => {
110
+ const execution = cellExecutionView(executions, cellId);
111
+ return (
112
+ isUninstantiated(execution) ||
124
113
  cellData[cellId].edited ||
125
- cellRuntime[cellId].interrupted ||
114
+ execution.outcome === "interrupted" ||
126
115
  (cellRuntime[cellId].staleInputs &&
127
116
  // if a cell is disabled, it can't be run ...
128
- !(
129
- cellRuntime[cellId].status === "disabled-transitively" ||
130
- cellData[cellId].config.disabled
131
- )),
132
- );
117
+ !(execution.blocked || cellData[cellId].config.disabled))
118
+ );
119
+ });
133
120
  }
134
121
 
122
+ /** Whether a cell has never run: no result to show and nothing in flight. */
135
123
  export function isUninstantiated({
136
- executionTime,
137
124
  status,
138
- errored,
139
- interrupted,
140
- stopped,
141
- }: {
142
- executionTime: number | null;
143
- status: RuntimeState;
144
- errored: boolean;
145
- interrupted: boolean;
146
- stopped: boolean;
147
- }) {
148
- return (
149
- // hasn't run ...
150
- executionTime === null &&
151
- // isn't currently queued/running &&
152
- status !== "queued" &&
153
- status !== "running" &&
154
- // and isn't in an error state.
155
- !(errored || interrupted || stopped)
156
- );
125
+ outcome,
126
+ }: Pick<CellExecutionView, "status" | "outcome">) {
127
+ return outcome === null && status !== "queued" && status !== "running";
157
128
  }
158
129
 
159
130
  /**
@@ -166,17 +137,15 @@ export function cellNeedsRun({
166
137
  interrupted,
167
138
  staleInputs,
168
139
  disabled,
169
- status,
140
+ blocked,
170
141
  }: {
171
142
  edited: boolean;
172
143
  interrupted: boolean;
173
144
  staleInputs: boolean;
174
145
  disabled: boolean | undefined;
175
- status: RuntimeState;
146
+ blocked: boolean;
176
147
  }): boolean {
177
- const disabledOrAncestorDisabled =
178
- disabled || status === "disabled-transitively";
179
- return edited || interrupted || (staleInputs && !disabledOrAncestorDisabled);
148
+ return edited || interrupted || (staleInputs && !(disabled || blocked));
180
149
  }
181
150
 
182
151
  export function cellStatusClasses({
@@ -184,20 +153,20 @@ export function cellStatusClasses({
184
153
  errored,
185
154
  stopped,
186
155
  disabled,
187
- status,
156
+ blocked,
188
157
  }: {
189
158
  needsRun: boolean;
190
159
  errored: boolean;
191
160
  stopped: boolean;
192
161
  disabled: boolean | undefined;
193
- status: RuntimeState;
162
+ blocked: boolean;
194
163
  }) {
195
164
  return {
196
165
  "needs-run": needsRun,
197
166
  "has-error": errored,
198
167
  stopped,
199
168
  disabled: disabled ?? false,
200
- stale: status === "disabled-transitively",
169
+ stale: blocked,
201
170
  };
202
171
  }
203
172
 
@@ -145,4 +145,63 @@ describe("clickablePlaceholderExtension", () => {
145
145
 
146
146
  view.destroy();
147
147
  });
148
+
149
+ describe("caret coordinates", () => {
150
+ const original = Range.prototype.getClientRects;
151
+ let rects: Array<Pick<DOMRect, "left" | "right" | "top" | "bottom">>;
152
+
153
+ beforeEach(() => {
154
+ rects = [{ left: 10, right: 50, top: 2, bottom: 18 }];
155
+ Range.prototype.getClientRects = () => rects as unknown as DOMRectList;
156
+ });
157
+
158
+ afterEach(() => {
159
+ Range.prototype.getClientRects = original;
160
+ });
161
+
162
+ function renderWidget(beforeText: string) {
163
+ const view = new EditorView({
164
+ state: EditorState.create({
165
+ doc: "",
166
+ extensions: clickablePlaceholderExtension({
167
+ beforeText,
168
+ linkText: "here",
169
+ afterText: "",
170
+ onClick: vi.fn(),
171
+ }),
172
+ }),
173
+ });
174
+ const widget = view.state
175
+ .facet(EditorView.decorations)
176
+ .flatMap((deco) => (typeof deco === "function" ? [deco(view)] : [deco]))
177
+ .map((set) => set.iter().value?.spec.widget)
178
+ .find(Boolean);
179
+ const dom = widget.toDOM(view);
180
+ document.body.append(dom);
181
+ return { view, widget, dom };
182
+ }
183
+
184
+ it("collapses the caret to the start of the first line of text", () => {
185
+ const { view, widget, dom } = renderWidget("Type or ");
186
+ expect(widget.coordsAt(dom, 0, 1)).toEqual({
187
+ left: 10,
188
+ right: 10,
189
+ top: 2,
190
+ bottom: 18,
191
+ });
192
+ dom.style.direction = "rtl";
193
+ expect(widget.coordsAt(dom, 0, 1)).toMatchObject({ left: 50, right: 50 });
194
+ dom.remove();
195
+ view.destroy();
196
+ });
197
+
198
+ it("skips empty text and falls back when nothing is laid out", () => {
199
+ const { view, widget, dom } = renderWidget("");
200
+ expect(widget.coordsAt(dom, 0, 1)).toMatchObject({ left: 10 });
201
+ rects = [];
202
+ expect(widget.coordsAt(dom, 0, 1)).toBeNull();
203
+ dom.remove();
204
+ view.destroy();
205
+ });
206
+ });
148
207
  });
@@ -90,6 +90,29 @@ export function clickablePlaceholderExtension(opts: {
90
90
  return wrapper;
91
91
  }
92
92
 
93
+ public override coordsAt(dom: HTMLElement) {
94
+ // A wrapped placeholder must not make the caret span every line.
95
+ const walker = document.createTreeWalker(dom, NodeFilter.SHOW_TEXT);
96
+ let text = walker.nextNode();
97
+ while (text && !text.textContent) {
98
+ text = walker.nextNode();
99
+ }
100
+ if (!text) {
101
+ return null;
102
+ }
103
+ const range = document.createRange();
104
+ range.selectNodeContents(text);
105
+ const rect = range.getClientRects()[0];
106
+ if (!rect) {
107
+ return null;
108
+ }
109
+ const left =
110
+ window.getComputedStyle(dom).direction === "rtl"
111
+ ? rect.right
112
+ : rect.left;
113
+ return { left, right: left, top: rect.top, bottom: rect.bottom };
114
+ }
115
+
93
116
  public override ignoreEvent(event: Event) {
94
117
  return (
95
118
  event.target instanceof Element &&
@@ -27,7 +27,8 @@ import { useSetOutputsExpanded } from "../components/editor/actions/useSetOutput
27
27
  import { CellArray } from "../components/editor/renderers/cell-array";
28
28
  import { CellsRenderer } from "../components/editor/renderers/cells-renderer";
29
29
  import { useHotkey } from "../hooks/useHotkey";
30
- import { hasCellsAtom, notebookIsRunningAtom } from "./cells/cells";
30
+ import { hasCellsAtom } from "./cells/cells";
31
+ import { notebookIsRunningAtom } from "./executions";
31
32
  import { numColumnsAtom } from "./cells/layout";
32
33
  import { useCellActions } from "./cells/actions";
33
34
  import type { AppConfig, UserConfig } from "./config/config-schema";