@marimo-team/islands 0.25.3-dev192 → 0.25.3-dev196

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 (154) hide show
  1. package/dist/{ConnectedDataExplorerComponent-BBbOElk3.js → ConnectedDataExplorerComponent-BRuwLOIx.js} +9 -9
  2. package/dist/{ErrorBoundary-Dw6uRKVk.js → ErrorBoundary-C3Xv3yzV.js} +2 -2
  3. package/dist/{abnfDiagram-VCTEODGH-DhMT6Uhd.js → abnfDiagram-VCTEODGH-LlJTOOU_.js} +2 -2
  4. package/dist/{architectureDiagram-5GKGNRK7-BVbEiJLq.js → architectureDiagram-5GKGNRK7-9PQtGLLZ.js} +1 -1
  5. package/dist/{arrays-R6JsImiT.js → arrays-DA6KkLfq.js} +6 -12
  6. package/dist/assets/__vite-browser-external-BCJsx8QS.js +1 -0
  7. package/dist/assets/{worker-C6e9ERyd.js → worker-DodjWT_Z.js} +2 -2
  8. package/dist/{capabilities-CI4xUPsp.js → capabilities-CvWU-ey_.js} +1 -1
  9. package/dist/{chat-ui-BguxTQEL.js → chat-ui-Bl2euMBD.js} +93 -93
  10. package/dist/{chunk-IMKFNOWR-By5hSbJJ.js → chunk-IMKFNOWR-DcXv-BD5.js} +1 -1
  11. package/dist/{chunk-SHT3W25Y-DFrEYG54.js → chunk-SHT3W25Y-VwnELN23.js} +1 -1
  12. package/dist/{chunk-SVP7TREG-DFXGc9ew.js → chunk-SVP7TREG-DgQpI5Zi.js} +1 -1
  13. package/dist/{chunk-TICWLB2K-DZdNIpoZ.js → chunk-TICWLB2K-CTg6VTJT.js} +1 -1
  14. package/dist/{classDiagram-ZZMXUADV-Cgj_eEQA.js → classDiagram-ZZMXUADV-UzmcFrnW.js} +2 -2
  15. package/dist/{classDiagram-v2-VYDZK3BY-DLAUrSqj.js → classDiagram-v2-VYDZK3BY-DhjiI6IQ.js} +2 -2
  16. package/dist/{code-visibility-v_7ijLtx.js → code-visibility-BCcqmP58.js} +38 -42
  17. package/dist/{common-BUwWHpnK.js → common-DrNB2bY2.js} +849 -849
  18. package/dist/{cynefinDiagram-5FMLGOSQ-Cikf9AKa.js → cynefinDiagram-5FMLGOSQ-O4ShIbJX.js} +1 -1
  19. package/dist/{dagre-GXQ25YYZ-C5KEi5_l.js → dagre-GXQ25YYZ-dtHhhcPu.js} +1 -1
  20. package/dist/{data-grid-overlay-editor-4IZymnXZ.js → data-grid-overlay-editor-TGXV_Hf4.js} +1 -1
  21. package/dist/{diagram-S7CK7UJ4-BaoBTQjR.js → diagram-S7CK7UJ4-ppPYVZ6W.js} +1 -1
  22. package/dist/{diagram-UQ7AKVKN-DDufBsUA.js → diagram-UQ7AKVKN-CMZDf--k.js} +1 -1
  23. package/dist/{diagram-VX7I27RA-CY6RALJ8.js → diagram-VX7I27RA-CFvJdx-C.js} +1 -1
  24. package/dist/{diagram-Z3DM3KII-Bc-LHsD6.js → diagram-Z3DM3KII-Dt6_uFbv.js} +1 -1
  25. package/dist/{dropdown-menu-drDjxJyV.js → dropdown-menu-CeaOsjWZ.js} +2 -2
  26. package/dist/{ebnfDiagram-PWID7BFC-Cz0J-tcC.js → ebnfDiagram-PWID7BFC-DpRFrKK-.js} +2 -2
  27. package/dist/{erDiagram-RLTQ6QDP-DTXTDgFu.js → erDiagram-RLTQ6QDP-CqAU4b0u.js} +1 -1
  28. package/dist/{error-entries-CZ1NQ6Kv.js → error-entries-CYGwr5-O.js} +5596 -6012
  29. package/dist/feedback-button-PTiPn2GP.js +374 -0
  30. package/dist/{flowDiagram-HODETNUW-RbwWf2zc.js → flowDiagram-HODETNUW-i8-Gw8HO.js} +2 -2
  31. package/dist/{formats-DbXnijtH.js → formats-BtoHHy42.js} +1 -1
  32. package/dist/{glide-data-editor-DU8_EDG5.js → glide-data-editor-BSyexjkB.js} +5 -5
  33. package/dist/{globals-BeBDOk22.js → globals-B7gkJAxm.js} +2 -2
  34. package/dist/{html-to-image-D_4_hQ8U.js → html-to-image-Cy5UDpQJ.js} +56 -56
  35. package/dist/{infoDiagram-27XIBGKW-DlmzZjmI.js → infoDiagram-27XIBGKW-DpfOKdLL.js} +1 -1
  36. package/dist/{ishikawaDiagram-5VMMS53U-EN3TYP_q.js → ishikawaDiagram-5VMMS53U-Dg7mrMG7.js} +1 -1
  37. package/dist/{kanban-definition-UXKFOSKX-CK_-Ru7Q.js → kanban-definition-UXKFOSKX-QB6e3Jrj.js} +1 -1
  38. package/dist/main.js +5875 -5876
  39. package/dist/{mermaid-DU9ZRmkG.js → mermaid-CeWG4Egr.js} +48 -48
  40. package/dist/{mindmap-definition-YA3MSWOX-C-hqgUvF.js → mindmap-definition-YA3MSWOX-BTz3uTGe.js} +1 -1
  41. package/dist/{number-overlay-editor-BoIkyh6H.js → number-overlay-editor-Bb91yQ46.js} +1 -1
  42. package/dist/{once-eG8Vmvjd.js → once-DWGO2mQ2.js} +1 -1
  43. package/dist/{pegDiagram-XKGWAZYB-BiMejYPB.js → pegDiagram-XKGWAZYB-3ZIEa3mB.js} +2 -2
  44. package/dist/{pieDiagram-E7YTZNPT-B1zCZ1eq.js → pieDiagram-E7YTZNPT-BJAlk046.js} +1 -1
  45. package/dist/{process-output-DxSCHBWp.js → process-output-q9MTAIMX.js} +2 -2
  46. package/dist/{railroadDiagram-O6MQD6OU-BLcfaASl.js → railroadDiagram-O6MQD6OU-Dn3bsSRa.js} +2 -2
  47. package/dist/{requirementDiagram-BXWQKSXE-DtBOf52-.js → requirementDiagram-BXWQKSXE-wcOmQQCL.js} +1 -1
  48. package/dist/{reveal-component-5M3a2rpj.js → reveal-component-vTdjiyDv.js} +17 -17
  49. package/dist/{sequenceDiagram-WJ2MYXX4-DIwF19_n.js → sequenceDiagram-WJ2MYXX4-DSh_qxoF.js} +1 -1
  50. package/dist/{spec-DZDnnlgT.js → spec-C7Fos-If.js} +1 -1
  51. package/dist/{stateDiagram-D77RDMKH-sWk1AdYL.js → stateDiagram-D77RDMKH-DH_H8NMH.js} +2 -2
  52. package/dist/{stateDiagram-v2-MP3YSRHH-CiNo-OI7.js → stateDiagram-v2-MP3YSRHH-D_JM7MEK.js} +2 -2
  53. package/dist/style.css +1 -1
  54. package/dist/{swimlanes-42K2YHIH-Bj3hiOY5.js → swimlanes-42K2YHIH-CfoTJO-9.js} +1 -1
  55. package/dist/{swimlanesDiagram-VR7AAH4N-BzEo2r_N.js → swimlanesDiagram-VR7AAH4N-Z_QhvK0G.js} +2 -2
  56. package/dist/{swiper-component-BkCm-kLg.js → swiper-component-D2geL1Ku.js} +1 -1
  57. package/dist/{timeline-definition-24CTP7MA-CrPQk42H.js → timeline-definition-24CTP7MA-BFtdScaL.js} +1 -1
  58. package/dist/{toDate-Cu2pIZcC.js → toDate-CQAFSwos.js} +24 -24
  59. package/dist/{useAsyncData-DEhLdRIu.js → useAsyncData-BeyZyjso.js} +1 -1
  60. package/dist/{useDeepCompareMemoize-RjZGVhBV.js → useDeepCompareMemoize-DOQgae0W.js} +1 -1
  61. package/dist/{useIframeCapabilities-CSdWn6NP.js → useIframeCapabilities-DNvaj9wM.js} +1 -1
  62. package/dist/{useLifecycle-DtcB2s1z.js → useLifecycle-n2lnCFS8.js} +1 -1
  63. package/dist/{useTheme-CoEtr4mK.js → useTheme-CY777LXN.js} +303 -99
  64. package/dist/{vega-component-BhjTrP9-.js → vega-component-0nQzA-I8.js} +6 -6
  65. package/dist/{vennDiagram-4TSXK5OY-BaYMoIpj.js → vennDiagram-4TSXK5OY-BXY8IGCA.js} +1 -1
  66. package/dist/{wardleyDiagram-VM6X3IG4-DjHeuj8p.js → wardleyDiagram-VM6X3IG4-D0tFe6eC.js} +1 -1
  67. package/dist/{xychartDiagram-S5SC5T6Z-Bc_hcuXS.js → xychartDiagram-S5SC5T6Z-Dx8wFM-y.js} +1 -1
  68. package/package.json +1 -1
  69. package/src/__mocks__/notebook.ts +0 -1
  70. package/src/__mocks__/partial.ts +26 -0
  71. package/src/__tests__/main.test.tsx +24 -1
  72. package/src/components/app-config/app-config-form.tsx +5 -9
  73. package/src/components/editor/__tests__/data-attributes.test.tsx +0 -1
  74. package/src/components/editor/__tests__/output-persistence.test.tsx +0 -1
  75. package/src/components/editor/actions/useCellActionButton.tsx +6 -5
  76. package/src/components/editor/actions/useConfigActions.tsx +3 -4
  77. package/src/components/editor/actions/useNotebookActions.tsx +5 -4
  78. package/src/components/editor/cell/code/cell-editor.tsx +13 -6
  79. package/src/components/editor/cell/collapse.tsx +6 -1
  80. package/src/components/editor/chrome/components/feedback-button.tsx +1 -7
  81. package/src/components/editor/controls/Controls.tsx +1 -1
  82. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +8 -6
  83. package/src/components/editor/navigation/__tests__/navigation.test.ts +15 -15
  84. package/src/components/editor/navigation/__tests__/selection.test.ts +44 -21
  85. package/src/components/editor/navigation/focus-utils.ts +19 -0
  86. package/src/components/editor/navigation/multi-cell-action-toolbar.tsx +6 -13
  87. package/src/components/editor/navigation/navigation.ts +52 -43
  88. package/src/components/editor/navigation/selection.ts +30 -29
  89. package/src/components/editor/notebook-cell.tsx +10 -18
  90. package/src/components/editor/renderers/cell-array.tsx +6 -5
  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 +1 -1
  93. package/src/components/pages/edit-page.tsx +0 -13
  94. package/src/components/scratchpad/scratchpad.tsx +0 -1
  95. package/src/components/slides/reveal-component.tsx +1 -1
  96. package/src/components/slides/slide-cell-view.tsx +0 -1
  97. package/src/components/sort/SortableCellsProvider.tsx +6 -5
  98. package/src/core/MarimoApp.tsx +1 -1
  99. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +0 -37
  100. package/src/core/cells/__tests__/actions.test.ts +190 -80
  101. package/src/core/cells/__tests__/arrange.test.ts +263 -0
  102. package/src/core/cells/__tests__/cells.test.ts +11 -1101
  103. package/src/core/cells/__tests__/document.test.ts +3 -32
  104. package/src/core/cells/__tests__/editor.test.ts +57 -0
  105. package/src/core/cells/__tests__/harness.ts +82 -0
  106. package/src/core/cells/__tests__/layout.test.ts +189 -0
  107. package/src/core/cells/__tests__/notebook-outline.test.ts +7 -1
  108. package/src/core/cells/__tests__/session.test.ts +329 -174
  109. package/src/core/cells/__tests__/utils.test.ts +0 -1
  110. package/src/core/cells/actions.ts +247 -82
  111. package/src/core/cells/arrange.ts +341 -0
  112. package/src/core/cells/cells.ts +27 -516
  113. package/src/core/cells/document.ts +6 -5
  114. package/src/core/cells/edit.ts +30 -0
  115. package/src/core/cells/editor.ts +82 -0
  116. package/src/core/cells/layout.ts +152 -0
  117. package/src/core/cells/session.ts +75 -52
  118. package/src/core/cells/types.ts +0 -5
  119. package/src/core/cells/utils.ts +6 -4
  120. package/src/core/config/__tests__/app-config.test.ts +64 -0
  121. package/src/core/config/__tests__/config-schema.test.ts +1 -1
  122. package/src/core/config/config-schema.ts +6 -2
  123. package/src/core/config/config.ts +26 -12
  124. package/src/core/islands/bootstrap.ts +0 -1
  125. package/src/core/kernel/handlers.ts +1 -13
  126. package/src/core/meta/__tests__/code-visibility.test.tsx +78 -70
  127. package/src/core/meta/code-visibility.ts +18 -9
  128. package/src/core/notebook/__tests__/operations.test.ts +51 -0
  129. package/src/core/notebook/document/__tests__/codemirror.test.ts +33 -7
  130. package/src/core/notebook/index.ts +2 -1
  131. package/src/core/notebook/operations.ts +30 -0
  132. package/src/core/saving/__tests__/state.test.ts +111 -0
  133. package/src/core/saving/save-component.tsx +19 -33
  134. package/src/core/saving/state.ts +38 -39
  135. package/src/core/saving/useAutoSave.ts +8 -35
  136. package/src/core/session/__tests__/backend.test.ts +1 -0
  137. package/src/core/session/__tests__/useNotebookReplica.hook.test.tsx +1 -0
  138. package/src/core/session/active-notebook.ts +11 -0
  139. package/src/core/session/backend.ts +17 -19
  140. package/src/core/session/embedded.ts +7 -0
  141. package/src/core/session/useNotebookReplica.ts +3 -1
  142. package/src/core/session/useNotebookSession.tsx +7 -8
  143. package/src/core/transports/__tests__/static.test.ts +139 -8
  144. package/src/core/transports/static.ts +98 -16
  145. package/src/core/wasm/__tests__/bridge.test.ts +1 -0
  146. package/src/mount.tsx +19 -10
  147. package/src/stories/cell.stories.tsx +0 -2
  148. package/src/stories/layout/vertical/one-column.stories.tsx +0 -2
  149. package/src/utils/__tests__/id-tree.test.ts +20 -1548
  150. package/src/utils/id-tree.tsx +12 -537
  151. package/dist/assets/__vite-browser-external-C8gQho07.js +0 -1
  152. package/dist/feedback-button-MpeoIn1D.js +0 -379
  153. package/src/components/editor/navigation/__tests__/state.test.ts +0 -81
  154. package/src/components/editor/navigation/state.ts +0 -65
@@ -13,7 +13,6 @@ import {
13
13
  } from "@/utils/id-tree";
14
14
  import { invariant } from "@/utils/invariant";
15
15
  import { Logger } from "@/utils/Logger";
16
- import { clamp } from "@/utils/math";
17
16
  import { Objects } from "@/utils/objects";
18
17
  import { extractAllTracebackInfo, type TracebackInfo } from "@/utils/traceback";
19
18
  import { createReducerAndAtoms } from "../../utils/createReducer";
@@ -22,7 +21,7 @@ import { editorMountScheduler } from "../codemirror/editor-mount-scheduler";
22
21
  import { prepareCellCodeUpdate } from "../codemirror/language/cell-code";
23
22
  import { splitEditor } from "../codemirror/language/utils";
24
23
  import type { SerializedEditorState } from "../codemirror/types";
25
- import { findCollapseRange, mergeOutlines } from "../dom/outline";
24
+ import { mergeOutlines } from "../dom/outline";
26
25
  import type { CellMessage } from "../kernel/messages";
27
26
  import { isErrorMime } from "../mime";
28
27
  import type { CellConfig } from "../network/types";
@@ -30,13 +29,11 @@ import { createDeepEqualAtom, store } from "../state/jotai";
30
29
  import { isWasm } from "../wasm/utils";
31
30
  import { prepareCellForExecution, transitionCell } from "./cell";
32
31
  import { applyDocument, documentEditMiddleware } from "./document";
32
+ import { isCodeHidden, shownHiddenCodeAtom } from "./editor";
33
33
  import { CellId, SCRATCH_CELL_ID } from "./ids";
34
34
  import { type CellLog, getCellLogsForMessage } from "./logs";
35
- import {
36
- focusAndScrollCellIntoView,
37
- scrollToBottom,
38
- scrollToTop,
39
- } from "./scrollCellIntoView";
35
+ import type { Outline } from "./outline";
36
+ import { focusAndScrollCellIntoView } from "./scrollCellIntoView";
40
37
  import {
41
38
  type CellData,
42
39
  type CellRuntimeState,
@@ -116,12 +113,6 @@ export interface NotebookState {
116
113
  * Logs of all cell messages
117
114
  */
118
115
  cellLogs: CellLog[];
119
- /**
120
- * Set of cells that have been created that are initialized with `hide_code`.
121
- *
122
- * These start temporarily open until their first blur event.
123
- */
124
- untouchedNewCells: Set<CellId>;
125
116
  }
126
117
 
127
118
  function withScratchCell(notebookState: NotebookState): NotebookState {
@@ -160,7 +151,6 @@ export function initialNotebookState(): NotebookState {
160
151
  history: [],
161
152
  scrollKey: null,
162
153
  cellLogs: [],
163
- untouchedNewCells: new Set<CellId>(),
164
154
  });
165
155
  }
166
156
 
@@ -174,226 +164,6 @@ const {
174
164
  useActions,
175
165
  valueAtom: notebookAtom,
176
166
  } = createReducerAndAtoms(initialNotebookState, {
177
- moveCell: (
178
- state,
179
- action: {
180
- cellId: CellId;
181
- before?: boolean;
182
- direction?: "left" | "right";
183
- },
184
- ) => {
185
- const { cellId, before, direction } = action;
186
-
187
- if (before !== undefined && direction !== undefined) {
188
- Logger.warn(
189
- "Both before and direction specified for moveCell. Ignoring one.",
190
- );
191
- }
192
-
193
- // Handle left/right movement
194
- if (direction) {
195
- const fromColumn = state.cellIds.findWithId(cellId);
196
- const fromColumnIndex = state.cellIds.indexOf(fromColumn);
197
- const toColumnIndex =
198
- direction === "left" ? fromColumnIndex - 1 : fromColumnIndex + 1;
199
- const toColumn = state.cellIds.at(toColumnIndex);
200
-
201
- // If no column to move to, return unchanged state
202
- if (!toColumn) {
203
- return state;
204
- }
205
-
206
- return {
207
- ...state,
208
- cellIds: state.cellIds.moveAcrossColumns(
209
- fromColumn.id,
210
- cellId,
211
- toColumn.id,
212
- undefined,
213
- ),
214
- scrollKey: cellId,
215
- };
216
- }
217
-
218
- // Handle up/down movement
219
- const column = state.cellIds.findWithId(cellId);
220
- const cellIndex = column.indexOfOrThrow(cellId);
221
-
222
- if (before && cellIndex === 0) {
223
- return {
224
- ...state,
225
- cellIds: state.cellIds.moveWithinColumn(column.id, cellIndex, 0),
226
- scrollKey: cellId,
227
- };
228
- }
229
- if (!before && cellIndex === column.length - 1) {
230
- return {
231
- ...state,
232
- cellIds: state.cellIds.moveWithinColumn(
233
- column.id,
234
- cellIndex,
235
- column.length - 1,
236
- ),
237
- scrollKey: cellId,
238
- };
239
- }
240
-
241
- return before
242
- ? {
243
- ...state,
244
- cellIds: state.cellIds.moveWithinColumn(
245
- column.id,
246
- cellIndex,
247
- cellIndex - 1,
248
- ),
249
- scrollKey: cellId,
250
- }
251
- : {
252
- ...state,
253
- cellIds: state.cellIds.moveWithinColumn(
254
- column.id,
255
- cellIndex,
256
- cellIndex + 1,
257
- ),
258
- scrollKey: cellId,
259
- };
260
- },
261
- moveCellToIndex: (
262
- state,
263
- action: {
264
- cellId: CellId;
265
- columnId: CellColumnId;
266
- index: number;
267
- },
268
- ) => {
269
- const { cellId, columnId, index } = action;
270
- const fromColumn = state.cellIds.findWithId(cellId);
271
- const fromIndex = fromColumn.indexOfOrThrow(cellId);
272
-
273
- const destinationColumn = state.cellIds.get(columnId);
274
- if (!destinationColumn) {
275
- return state;
276
- }
277
-
278
- const clampedIndex = Math.max(0, Math.min(index, destinationColumn.length));
279
- const adjustedIndex =
280
- fromColumn.id === columnId && fromIndex < clampedIndex
281
- ? clampedIndex - 1
282
- : clampedIndex;
283
-
284
- if (fromColumn.id === columnId && fromIndex === adjustedIndex) {
285
- return state;
286
- }
287
-
288
- const withoutCell = state.cellIds.deleteById(cellId);
289
- const updatedColumn = withoutCell.get(columnId);
290
- if (!updatedColumn) {
291
- return state;
292
- }
293
-
294
- return {
295
- ...state,
296
- cellIds: withoutCell.insertId(
297
- cellId,
298
- columnId,
299
- Math.max(0, Math.min(adjustedIndex, updatedColumn.length)),
300
- ),
301
- scrollKey: null,
302
- };
303
- },
304
- dropCellOverCell: (state, action: { cellId: CellId; overCellId: CellId }) => {
305
- const { cellId, overCellId } = action;
306
-
307
- const fromColumn = state.cellIds.findWithId(cellId);
308
- const toColumn = state.cellIds.findWithId(overCellId);
309
-
310
- const fromIndex = fromColumn.indexOfOrThrow(cellId);
311
- const toIndex = toColumn.indexOfOrThrow(overCellId);
312
-
313
- if (fromColumn.id === toColumn.id) {
314
- if (fromIndex === toIndex) {
315
- return state;
316
- }
317
- return {
318
- ...state,
319
- cellIds: state.cellIds.moveWithinColumn(
320
- fromColumn.id,
321
- fromIndex,
322
- toIndex,
323
- ),
324
- scrollKey: null,
325
- };
326
- }
327
-
328
- return {
329
- ...state,
330
- cellIds: state.cellIds.moveAcrossColumns(
331
- fromColumn.id,
332
- cellId,
333
- toColumn.id,
334
- overCellId,
335
- ),
336
- scrollKey: null,
337
- };
338
- },
339
- moveCellsRelativeTo: (
340
- state,
341
- action: {
342
- cellIds: CellId[];
343
- targetCellId: CellId;
344
- position: "before" | "after";
345
- previousPlacements?: Array<{ columnId: CellColumnId; index: CellIndex }>;
346
- },
347
- ) => {
348
- const { cellIds, targetCellId, position, previousPlacements } = action;
349
- if (cellIds.length === 0) {
350
- return state;
351
- }
352
- const newCellIds = state.cellIds.moveCellsRelativeTo(
353
- cellIds,
354
- targetCellId,
355
- position,
356
- );
357
- // Only record undo when caller provided full before-state
358
- const canUndoMove =
359
- previousPlacements && previousPlacements.length === cellIds.length;
360
- const history = canUndoMove
361
- ? [
362
- ...state.history,
363
- { type: "move" as const, cellIds, placements: previousPlacements },
364
- ]
365
- : state.history;
366
- return {
367
- ...state,
368
- cellIds: newCellIds,
369
- history,
370
- scrollKey: null,
371
- };
372
- },
373
- dropCellOverColumn: (
374
- state,
375
- action: { cellId: CellId; columnId: CellColumnId },
376
- ) => {
377
- const { cellId, columnId } = action;
378
- const fromColumn = state.cellIds.findWithId(cellId);
379
-
380
- return {
381
- ...state,
382
- cellIds: state.cellIds.moveAcrossColumns(
383
- fromColumn.id,
384
- cellId,
385
- columnId,
386
- undefined,
387
- ),
388
- };
389
- },
390
- dropOverNewColumn: (state, action: { cellId: CellId }) => {
391
- const { cellId } = action;
392
- return {
393
- ...state,
394
- cellIds: state.cellIds.moveToNewColumn(cellId),
395
- };
396
- },
397
167
  moveColumn: (
398
168
  state,
399
169
  action: {
@@ -409,112 +179,6 @@ const {
409
179
  cellIds: state.cellIds.moveColumn(action.column, action.overColumn),
410
180
  };
411
181
  },
412
- focusCell: (
413
- state,
414
- action: { cellId: CellId; where: "before" | "after" | "exact" },
415
- ) => {
416
- const column = state.cellIds.findWithId(action.cellId);
417
- if (column.length === 0) {
418
- return state;
419
- }
420
-
421
- const { cellId, where } = action;
422
- const index = column.indexOfOrThrow(cellId);
423
-
424
- let focusIndex: number;
425
- if (where === "before") {
426
- focusIndex = clamp(index - 1, 0, column.length - 1);
427
- } else if (where === "after") {
428
- focusIndex = clamp(index + 1, 0, column.length - 1);
429
- } else {
430
- focusIndex = index;
431
- }
432
- const focusCellId = column.atOrThrow(focusIndex);
433
- // can scroll immediately, without setting scrollKey in state, because
434
- // CellArray won't need to re-render
435
- focusAndScrollCellIntoView({
436
- cellId: focusCellId,
437
- cell: state.cellHandles[focusCellId],
438
- isCodeHidden: isCellCodeHidden(state, focusCellId),
439
- codeFocus: where === "after" ? "top" : "bottom",
440
- variableName: undefined,
441
- });
442
- return state;
443
- },
444
- focusTopCell: (state) => {
445
- // TODO: focus the existing column, not the first column
446
- const column = state.cellIds.getColumns().at(0);
447
- if (column === undefined || column.length === 0) {
448
- return state;
449
- }
450
-
451
- const cellId = column.first();
452
- focusAndScrollCellIntoView({
453
- cellId: cellId,
454
- cell: state.cellHandles[cellId],
455
- isCodeHidden: isCellCodeHidden(state, cellId),
456
- codeFocus: undefined,
457
- variableName: undefined,
458
- });
459
- scrollToTop();
460
- return state;
461
- },
462
- focusBottomCell: (state) => {
463
- // TODO: focus the existing column, not the last column
464
- const column = state.cellIds.getColumns().at(-1);
465
- if (column === undefined || column.length === 0) {
466
- return state;
467
- }
468
-
469
- const cellId = column.last();
470
- focusAndScrollCellIntoView({
471
- cellId: cellId,
472
- cell: state.cellHandles[cellId],
473
- isCodeHidden: isCellCodeHidden(state, cellId),
474
- codeFocus: undefined,
475
- variableName: undefined,
476
- });
477
- scrollToBottom();
478
- return state;
479
- },
480
- sendToTop: (state, action: { cellId: CellId; scroll?: boolean }) => {
481
- const column = state.cellIds.findWithId(action.cellId);
482
- if (column.length === 0) {
483
- return state;
484
- }
485
-
486
- const { cellId, scroll = true } = action;
487
- const cellIndex = column.indexOfOrThrow(cellId);
488
- if (cellIndex === 0) {
489
- return state;
490
- }
491
-
492
- return {
493
- ...state,
494
- cellIds: state.cellIds.moveWithinColumn(column.id, cellIndex, 0),
495
- scrollKey: scroll ? cellId : null,
496
- };
497
- },
498
- sendToBottom: (state, action: { cellId: CellId; scroll?: boolean }) => {
499
- const column = state.cellIds.findWithId(action.cellId);
500
- if (column.length === 0) {
501
- return state;
502
- }
503
-
504
- const { cellId, scroll = true } = action;
505
- const cellIndex = column.indexOfOrThrow(cellId);
506
- const newIndex = column.length - 1;
507
-
508
- if (cellIndex === newIndex) {
509
- return state;
510
- }
511
-
512
- return {
513
- ...state,
514
- cellIds: state.cellIds.moveWithinColumn(column.id, cellIndex, newIndex),
515
- scrollKey: scroll ? cellId : null,
516
- };
517
- },
518
182
  addColumn: (state, action: { columnId: CellColumnId }) => {
519
183
  // Add column and new cell
520
184
  const newCellId = CellId.create();
@@ -573,39 +237,6 @@ const {
573
237
  *
574
238
  * TODO(MO-8028): Move to `undoDeleteCellAtom` with the order family.
575
239
  */
576
- undoMoveCells: (state, action: UndoMoveEntry) => {
577
- const { cellIds, placements } = action;
578
- if (
579
- cellIds.length === 0 ||
580
- placements.length !== cellIds.length ||
581
- cellIds.some((id) => !state.cellData[id])
582
- ) {
583
- return state;
584
- }
585
- const toRestore = cellIds.map((id, i) => ({
586
- id,
587
- columnId: placements[i].columnId,
588
- index: placements[i].index,
589
- }));
590
- return {
591
- ...state,
592
- cellIds: state.cellIds.placeCells(toRestore),
593
- scrollKey: cellIds[0] ?? null,
594
- };
595
- },
596
- clearSerializedEditorState: (state, action: { cellId: CellId }) => {
597
- const { cellId } = action;
598
- return updateCellData({
599
- state,
600
- cellId,
601
- cellReducer: (cell) => {
602
- return {
603
- ...cell,
604
- serializedEditorState: null,
605
- };
606
- },
607
- });
608
- },
609
240
  updateCellCode: (
610
241
  state,
611
242
  action: {
@@ -781,34 +412,6 @@ const {
781
412
  }
782
413
  return { ...state, cellRuntime };
783
414
  },
784
- markTouched: (state, action: { cellId: CellId }) => {
785
- const { cellId } = action;
786
-
787
- if (state.untouchedNewCells.has(cellId)) {
788
- const nextUntouchedNewCells = new Set(state.untouchedNewCells);
789
- nextUntouchedNewCells.delete(cellId);
790
- return {
791
- ...state,
792
- untouchedNewCells: nextUntouchedNewCells,
793
- };
794
- }
795
-
796
- return state;
797
- },
798
- markUntouched: (state, action: { cellId: CellId }) => {
799
- const { cellId } = action;
800
-
801
- if (!state.untouchedNewCells.has(cellId)) {
802
- const nextUntouchedNewCells = new Set(state.untouchedNewCells);
803
- nextUntouchedNewCells.add(cellId);
804
- return {
805
- ...state,
806
- untouchedNewCells: nextUntouchedNewCells,
807
- };
808
- }
809
-
810
- return state;
811
- },
812
415
  scrollToTarget: (state) => {
813
416
  // Scroll to the specified cell and clear the scroll key.
814
417
  const scrollKey = state.scrollKey;
@@ -825,7 +428,11 @@ const {
825
428
  focusAndScrollCellIntoView({
826
429
  cellId: cellId,
827
430
  cell: state.cellHandles[cellId],
828
- isCodeHidden: isCellCodeHidden(state, cellId),
431
+ isCodeHidden: isCodeHidden(
432
+ state.cellData[cellId]?.config.hide_code,
433
+ store.get(shownHiddenCodeAtom),
434
+ cellId,
435
+ ),
829
436
  codeFocus: undefined,
830
437
  variableName: undefined,
831
438
  });
@@ -855,108 +462,6 @@ const {
855
462
  cellLogs: [],
856
463
  };
857
464
  },
858
- collapseCell: (state, action: { cellId: CellId }) => {
859
- const { cellId } = action;
860
- const column = state.cellIds.findWithId(cellId);
861
-
862
- // Get all the top-level outlines
863
- const outlines = column.topLevelIds.map((id) => {
864
- const cell = state.cellRuntime[id];
865
- return cell.outline;
866
- });
867
-
868
- // Find the start/end of the collapsed range
869
- const startIndex = column.indexOfOrThrow(cellId);
870
- const range = findCollapseRange(startIndex, outlines);
871
- if (!range) {
872
- return state;
873
- }
874
- const endCellId = column.atOrThrow(range[1]);
875
-
876
- return {
877
- ...state,
878
- // Collapse the range
879
- cellIds: state.cellIds.transformWithCellId(cellId, (column) =>
880
- column.collapse(cellId, endCellId),
881
- ),
882
- scrollKey: cellId,
883
- };
884
- },
885
- expandCell: (state, action: { cellId: CellId }) => {
886
- const { cellId } = action;
887
- return {
888
- ...state,
889
- cellIds: state.cellIds.transformWithCellId(cellId, (column) =>
890
- column.expand(cellId),
891
- ),
892
- scrollKey: cellId,
893
- };
894
- },
895
- collapseAllCells: (state) => {
896
- return {
897
- ...state,
898
- cellIds: state.cellIds.transformAll((column) => {
899
- // Get all the top-level outlines
900
- const outlines = column.topLevelIds.map((id) => {
901
- const cell = state.cellRuntime[id];
902
- return cell.outline;
903
- });
904
-
905
- // Find the start/end of the collapsed ranges
906
- const nodes = [...column.nodes];
907
- const visibleEndByOriginalEnd = new Map<CellIndex, CellIndex>();
908
- const reversedCollapseRanges = [];
909
-
910
- // Iterate in reverse order (bottom-up) to process children first
911
- let i = nodes.length - 1;
912
- while (i >= 0) {
913
- const range = findCollapseRange(i, outlines);
914
- if (range) {
915
- const startIndex = i;
916
- const originalEnd = range[1];
917
- const endIndex =
918
- visibleEndByOriginalEnd.get(originalEnd) ?? originalEnd;
919
-
920
- // After this section collapses, its heading represents this
921
- // endpoint for any enclosing section that shares it.
922
- visibleEndByOriginalEnd.set(originalEnd, startIndex);
923
-
924
- // Add the range to the list of ranges
925
- const cellId = column.atOrThrow(startIndex);
926
- const until = column.atOrThrow(endIndex);
927
- reversedCollapseRanges.push({ id: cellId, until });
928
- } else {
929
- reversedCollapseRanges.push(null);
930
- }
931
- i--;
932
- }
933
-
934
- const collapseRanges = reversedCollapseRanges.toReversed();
935
- return column.collapseAll(collapseRanges);
936
- }),
937
- };
938
- },
939
- expandAllCells: (state) => {
940
- return {
941
- ...state,
942
- cellIds: state.cellIds.transformAll((column) => column.expandAll()),
943
- };
944
- },
945
- showCellIfHidden: (state, action: { cellId: CellId }) => {
946
- const { cellId } = action;
947
- const column = state.cellIds.findWithId(cellId);
948
- const prev = column;
949
- const result = column.findAndExpandDeep(cellId);
950
-
951
- if (result.equals(prev)) {
952
- return state;
953
- }
954
-
955
- return {
956
- ...state,
957
- cellIds: state.cellIds.transformWithCellId(cellId, () => result),
958
- };
959
- },
960
465
  splitCell: (state, action: { cellId: CellId }) => {
961
466
  const { cellId } = action;
962
467
  const column = state.cellIds.findWithId(cellId);
@@ -1108,13 +613,6 @@ const {
1108
613
  // strictly type the dispatched actions without creating a circular dependency.
1109
614
  addMiddleware(documentEditMiddleware);
1110
615
 
1111
- function isCellCodeHidden(state: NotebookState, cellId: CellId): boolean {
1112
- return (
1113
- Boolean(state.cellData[cellId].config.hide_code) &&
1114
- !state.untouchedNewCells.has(cellId)
1115
- );
1116
- }
1117
-
1118
616
  // Helper function to update a cell in the array
1119
617
  function updateCellRuntimeState({
1120
618
  state,
@@ -1266,6 +764,24 @@ export const cellErrorsAtom = atom((get) => {
1266
764
  return errors;
1267
765
  });
1268
766
 
767
+ /**
768
+ * The outline of each cell whose output has a heading. Compared by value, so
769
+ * readers are left alone by the runtime messages that change no heading.
770
+ */
771
+ export const cellOutlinesAtom = createDeepEqualAtom(
772
+ atom((get) => {
773
+ const outlines: Partial<Record<CellId, Outline>> = {};
774
+ for (const [id, runtime] of Objects.entries(
775
+ get(notebookAtom).cellRuntime,
776
+ )) {
777
+ if (runtime.outline !== null && runtime.outline.items.length > 0) {
778
+ outlines[id] = runtime.outline;
779
+ }
780
+ }
781
+ return outlines;
782
+ }),
783
+ );
784
+
1269
785
  export const notebookOutline = atom((get) => {
1270
786
  const cellIds = get(cellIdsAtom);
1271
787
  const cellRuntime = get(cellsRuntimeAtom);
@@ -1445,10 +961,6 @@ export function flattenTopLevelNotebookCells(
1445
961
  );
1446
962
  }
1447
963
 
1448
- export function createUntouchedCellAtom(cellId: CellId): Atom<boolean> {
1449
- return atom((get) => get(notebookAtom).untouchedNewCells.has(cellId));
1450
- }
1451
-
1452
964
  export function createTracebackInfoAtom(
1453
965
  cellId: CellId,
1454
966
  ): Atom<TracebackInfo[] | undefined> {
@@ -1523,7 +1035,6 @@ export const exportedForTesting = {
1523
1035
  reducer,
1524
1036
  createActions,
1525
1037
  initialNotebookState,
1526
- isCellCodeHidden,
1527
1038
  // Export atom families for testing cleanup
1528
1039
  cellDataAtom,
1529
1040
  cellRuntimeAtom,
@@ -42,7 +42,7 @@ type CellAction = DispatchedActionOf<ReducerCellActions>;
42
42
 
43
43
  // Following the document ----------------------------------------------------
44
44
 
45
- /** Rebuild the tree for `columns`, keeping column ids and collapsed sections. */
45
+ /** Rebuild the tree for `columns`, keeping column ids. */
46
46
  function reconcileColumns(
47
47
  current: MultiColumn<CellId>,
48
48
  columns: CellId[][],
@@ -59,9 +59,7 @@ function reconcileColumns(
59
59
  return current;
60
60
  }
61
61
  return new MultiColumn(
62
- columns.map((ids, index) =>
63
- CollapsibleTree.fromWithPreviousShape(ids, previous[index]),
64
- ),
62
+ columns.map((ids, index) => CollapsibleTree.from(ids, previous[index]?.id)),
65
63
  );
66
64
  }
67
65
 
@@ -139,7 +137,10 @@ export function applyDocument(
139
137
  edited: code.trim().length > 0,
140
138
  }),
141
139
  };
142
- cellRuntime = { ...cellRuntime, [id]: createCellRuntimeState() };
140
+ cellRuntime = {
141
+ ...cellRuntime,
142
+ [id]: cellRuntime[id] ?? createCellRuntimeState(),
143
+ };
143
144
  cellHandles = { ...cellHandles, [id]: createRef() };
144
145
  continue;
145
146
  }
@@ -0,0 +1,30 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ /** The active document model, as the notebook operations reach it. */
4
+
5
+ import type { Getter, Setter } from "jotai";
6
+ import type { DocumentModel, EditableDocument } from "@/core/notebook/document";
7
+ import { activeDocumentModelAtom } from "../session/active-notebook";
8
+ import { notebookAtom } from "./cells";
9
+ import type { CellId } from "./ids";
10
+
11
+ export function modelOf(get: Getter): DocumentModel {
12
+ const model = get(activeDocumentModelAtom);
13
+ if (!model) {
14
+ throw new Error("The notebook has no document to edit");
15
+ }
16
+ return model;
17
+ }
18
+
19
+ export function documentOf(get: Getter): EditableDocument {
20
+ const document = get(modelOf(get).documentAtom);
21
+ if (!document) {
22
+ throw new Error("The notebook has no document to edit");
23
+ }
24
+ return document;
25
+ }
26
+
27
+ /** Scroll to and focus the cell once it mounts. */
28
+ export function focusWhenMounted(set: Setter, cellId: CellId): void {
29
+ set(notebookAtom, (state) => ({ ...state, scrollKey: cellId }));
30
+ }