@marimo-team/islands 0.25.3-dev197 → 0.25.3-dev198

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 (135) hide show
  1. package/dist/{ConnectedDataExplorerComponent-BRuwLOIx.js → ConnectedDataExplorerComponent-BsWiIPFk.js} +4 -4
  2. package/dist/{ErrorBoundary-jQVF8yMb.js → ErrorBoundary-D1Z3KAf_.js} +2 -2
  3. package/dist/{abnfDiagram-VCTEODGH-LlJTOOU_.js → abnfDiagram-VCTEODGH-I2x4Yu25.js} +2 -2
  4. package/dist/{architectureDiagram-5GKGNRK7-9PQtGLLZ.js → architectureDiagram-5GKGNRK7-1bSQMKzK.js} +1 -1
  5. package/dist/{arrays-DA6KkLfq.js → arrays-DoQ-AILx.js} +7 -21
  6. package/dist/{capabilities-CvWU-ey_.js → capabilities-C2QUNRXi.js} +1 -1
  7. package/dist/{chat-ui-Bl2euMBD.js → chat-ui-BAyHM_GD.js} +524 -524
  8. package/dist/{chunk-IMKFNOWR-DcXv-BD5.js → chunk-IMKFNOWR-fsCGAJ9G.js} +1 -1
  9. package/dist/{chunk-SHT3W25Y-VwnELN23.js → chunk-SHT3W25Y-D2STW4hj.js} +1 -1
  10. package/dist/{chunk-SVP7TREG-DgQpI5Zi.js → chunk-SVP7TREG-CiY7cQG-.js} +1 -1
  11. package/dist/{chunk-TICWLB2K-CTg6VTJT.js → chunk-TICWLB2K-C_t5RBp9.js} +1 -1
  12. package/dist/{classDiagram-ZZMXUADV-UzmcFrnW.js → classDiagram-ZZMXUADV-B2JvZAP4.js} +2 -2
  13. package/dist/{classDiagram-v2-VYDZK3BY-DhjiI6IQ.js → classDiagram-v2-VYDZK3BY-L8VsZG5A.js} +2 -2
  14. package/dist/{code-visibility-CjZo_SV8.js → code-visibility-CInEQd1e.js} +19 -19
  15. package/dist/{common-CFLvScF3.js → common-W5HfVJPN.js} +4362 -4362
  16. package/dist/{cynefinDiagram-5FMLGOSQ-O4ShIbJX.js → cynefinDiagram-5FMLGOSQ-DdllC264.js} +1 -1
  17. package/dist/{dagre-GXQ25YYZ-dtHhhcPu.js → dagre-GXQ25YYZ-BaWHw4_f.js} +1 -1
  18. package/dist/{data-grid-overlay-editor-CzW3mY6m.js → data-grid-overlay-editor-BQCaV0fI.js} +1 -1
  19. package/dist/{diagram-S7CK7UJ4-ppPYVZ6W.js → diagram-S7CK7UJ4-pFt-tUDe.js} +1 -1
  20. package/dist/{diagram-UQ7AKVKN-CMZDf--k.js → diagram-UQ7AKVKN-DqGJiBp4.js} +1 -1
  21. package/dist/{diagram-VX7I27RA-CFvJdx-C.js → diagram-VX7I27RA-Dr-yzMjf.js} +1 -1
  22. package/dist/{diagram-Z3DM3KII-Dt6_uFbv.js → diagram-Z3DM3KII-8xzJzv-w.js} +1 -1
  23. package/dist/{dropdown-menu-CeaOsjWZ.js → dropdown-menu-NlnsmNA6.js} +2 -2
  24. package/dist/{ebnfDiagram-PWID7BFC-DpRFrKK-.js → ebnfDiagram-PWID7BFC-Br04Wy-E.js} +2 -2
  25. package/dist/{erDiagram-RLTQ6QDP-CqAU4b0u.js → erDiagram-RLTQ6QDP-D5-XVgvv.js} +1 -1
  26. package/dist/{error-entries-CYGwr5-O.js → error-entries-BacCWMWj.js} +4678 -5028
  27. package/dist/{feedback-button-GR_8U4Vo.js → feedback-button-DLXUcQ7l.js} +113 -113
  28. package/dist/{flowDiagram-HODETNUW-i8-Gw8HO.js → flowDiagram-HODETNUW-CVl2kREF.js} +2 -2
  29. package/dist/{formats-BtoHHy42.js → formats-BCYthwyc.js} +1 -1
  30. package/dist/{glide-data-editor-Dfdxc22H.js → glide-data-editor-D5YaHPKx.js} +5 -5
  31. package/dist/{globals-CxjzBEbP.js → globals-DjX1uxzy.js} +2 -2
  32. package/dist/{html-to-image-Cy5UDpQJ.js → html-to-image-DAmgSo5P.js} +624 -573
  33. package/dist/{infoDiagram-27XIBGKW-DpfOKdLL.js → infoDiagram-27XIBGKW-BJ4vNcZE.js} +1 -1
  34. package/dist/{ishikawaDiagram-5VMMS53U-Dg7mrMG7.js → ishikawaDiagram-5VMMS53U-B_Jy1Dsm.js} +1 -1
  35. package/dist/{kanban-definition-UXKFOSKX-QB6e3Jrj.js → kanban-definition-UXKFOSKX-C6MkS9vw.js} +1 -1
  36. package/dist/main.js +1117 -1117
  37. package/dist/{mermaid-CeWG4Egr.js → mermaid-Bh918qrq.js} +34 -34
  38. package/dist/{mindmap-definition-YA3MSWOX-BTz3uTGe.js → mindmap-definition-YA3MSWOX-BP3ZTuir.js} +1 -1
  39. package/dist/{number-overlay-editor-CwHYeI4e.js → number-overlay-editor-3C_K6w_0.js} +1 -1
  40. package/dist/{once-DWGO2mQ2.js → once-NnpX_HnX.js} +1 -1
  41. package/dist/{pegDiagram-XKGWAZYB-3ZIEa3mB.js → pegDiagram-XKGWAZYB-nNGOQtnt.js} +2 -2
  42. package/dist/{pieDiagram-E7YTZNPT-BJAlk046.js → pieDiagram-E7YTZNPT-CpRz5BZz.js} +1 -1
  43. package/dist/{process-output-q9MTAIMX.js → process-output-DweUmAmV.js} +3 -3
  44. package/dist/{railroadDiagram-O6MQD6OU-Dn3bsSRa.js → railroadDiagram-O6MQD6OU-BqOEN4I0.js} +2 -2
  45. package/dist/{requirementDiagram-BXWQKSXE-wcOmQQCL.js → requirementDiagram-BXWQKSXE-CEfg6vu9.js} +1 -1
  46. package/dist/{reveal-component-C5bOqXlB.js → reveal-component-JjJRTT9d.js} +17 -17
  47. package/dist/{sequenceDiagram-WJ2MYXX4-DSh_qxoF.js → sequenceDiagram-WJ2MYXX4-IOmpQcOi.js} +1 -1
  48. package/dist/{spec-C7Fos-If.js → spec-Dkp64vVQ.js} +1 -1
  49. package/dist/{stateDiagram-D77RDMKH-DH_H8NMH.js → stateDiagram-D77RDMKH-tgU-EY9_.js} +2 -2
  50. package/dist/{stateDiagram-v2-MP3YSRHH-D_JM7MEK.js → stateDiagram-v2-MP3YSRHH-BofhG6q1.js} +2 -2
  51. package/dist/{swimlanes-42K2YHIH-CfoTJO-9.js → swimlanes-42K2YHIH-DW4vWDBd.js} +1 -1
  52. package/dist/{swimlanesDiagram-VR7AAH4N-Z_QhvK0G.js → swimlanesDiagram-VR7AAH4N-BJcrqUJr.js} +2 -2
  53. package/dist/{swiper-component-D2geL1Ku.js → swiper-component-BVfnhbnt.js} +1 -1
  54. package/dist/{timeline-definition-24CTP7MA-BFtdScaL.js → timeline-definition-24CTP7MA-D_QU3NGs.js} +1 -1
  55. package/dist/{toDate-CQAFSwos.js → toDate-BNDEXbfV.js} +30 -30
  56. package/dist/{useAsyncData-BeyZyjso.js → useAsyncData-ASDni9vH.js} +1 -1
  57. package/dist/{useDeepCompareMemoize-DOQgae0W.js → useDeepCompareMemoize-B1Ogyf53.js} +1 -1
  58. package/dist/{useIframeCapabilities-DNvaj9wM.js → useIframeCapabilities-6a_yDNXp.js} +1 -1
  59. package/dist/{useLifecycle-n2lnCFS8.js → useLifecycle-Bfpa2m7k.js} +1 -1
  60. package/dist/{useTheme-CY777LXN.js → useTheme-5TwyOh3B.js} +66 -66
  61. package/dist/{vega-component-0nQzA-I8.js → vega-component-BHS9UOtC.js} +19 -19
  62. package/dist/{vennDiagram-4TSXK5OY-BXY8IGCA.js → vennDiagram-4TSXK5OY-YFYsNIAh.js} +1 -1
  63. package/dist/{wardleyDiagram-VM6X3IG4-D0tFe6eC.js → wardleyDiagram-VM6X3IG4-i_n8mAWC.js} +1 -1
  64. package/dist/{xychartDiagram-S5SC5T6Z-Dx8wFM-y.js → xychartDiagram-S5SC5T6Z-BYGN95wW.js} +1 -1
  65. package/package.json +1 -1
  66. package/src/__mocks__/document.tsx +13 -4
  67. package/src/__mocks__/notebook.ts +1 -2
  68. package/src/components/datasources/datasources.tsx +1 -1
  69. package/src/components/dependency-graph/custom-node.tsx +1 -1
  70. package/src/components/dependency-graph/minimap-content.tsx +12 -14
  71. package/src/components/editor/actions/useSetCodeVisibility.ts +1 -1
  72. package/src/components/editor/actions/useSetOutputsExpanded.ts +1 -1
  73. package/src/components/editor/alerts/stdin-blocking-alert.tsx +1 -1
  74. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  75. package/src/components/editor/cell/useSplitCell.tsx +8 -5
  76. package/src/components/editor/chrome/panels/dependency-graph-panel.tsx +1 -1
  77. package/src/components/editor/chrome/panels/session-panel.tsx +1 -1
  78. package/src/components/editor/chrome/panels/variable-panel.tsx +1 -5
  79. package/src/components/editor/chrome/wrapper/minimap-state.ts +1 -1
  80. package/src/components/editor/columns/__tests__/cell-column.test.tsx +1 -1
  81. package/src/components/editor/columns/cell-column.tsx +1 -1
  82. package/src/components/editor/columns/sortable-column.tsx +1 -1
  83. package/src/components/editor/links/__tests__/cell-link.test.tsx +1 -1
  84. package/src/components/editor/links/cell-link-list.tsx +1 -1
  85. package/src/components/editor/links/cell-link.tsx +1 -1
  86. package/src/components/editor/navigation/__tests__/navigation.test.ts +20 -6
  87. package/src/components/editor/navigation/__tests__/selection.test.ts +1 -2
  88. package/src/components/editor/navigation/clipboard.ts +5 -3
  89. package/src/components/editor/renderers/cell-array.tsx +7 -12
  90. package/src/components/editor/renderers/slides-layout/slides-layout.tsx +1 -1
  91. package/src/components/editor/renderers/vertical-layout/__tests__/useFocusFirstEditor.test.ts +4 -4
  92. package/src/components/editor/renderers/vertical-layout/useFocusFirstEditor.ts +1 -1
  93. package/src/components/editor/stdin-blocking-alert.tsx +1 -1
  94. package/src/components/slides/__tests__/minimap-actions.test.tsx +13 -4
  95. package/src/components/slides/__tests__/minimap.test.ts +38 -19
  96. package/src/components/slides/minimap.tsx +11 -10
  97. package/src/components/sort/SortableCellsProvider.tsx +2 -21
  98. package/src/components/tracing/tracing.tsx +1 -1
  99. package/src/core/ai/__tests__/staged-cells.test.ts +1 -1
  100. package/src/core/ai/context/providers/__tests__/cell-output.test.ts +8 -12
  101. package/src/core/ai/context/providers/cell-output.ts +2 -2
  102. package/src/core/ai/tools/__tests__/edit-notebook-tool.test.ts +34 -66
  103. package/src/core/ai/tools/edit-notebook-tool.ts +12 -10
  104. package/src/core/cells/__tests__/actions.test.ts +64 -25
  105. package/src/core/cells/__tests__/arrange.test.ts +156 -4
  106. package/src/core/cells/__tests__/cells.test.ts +12 -290
  107. package/src/core/cells/__tests__/document.test.ts +2 -46
  108. package/src/core/cells/__tests__/harness.ts +28 -4
  109. package/src/core/cells/__tests__/layout.test.ts +42 -54
  110. package/src/core/cells/__tests__/session.test.ts +0 -2
  111. package/src/core/cells/__tests__/utils.test.ts +9 -107
  112. package/src/core/cells/actions.ts +112 -26
  113. package/src/core/cells/add-missing-import.ts +1 -1
  114. package/src/core/cells/arrange.ts +206 -13
  115. package/src/core/cells/cells.ts +28 -256
  116. package/src/core/cells/document.ts +18 -122
  117. package/src/core/cells/layout.ts +79 -10
  118. package/src/core/cells/pending-delete-service.ts +1 -1
  119. package/src/core/cells/utils.ts +4 -4
  120. package/src/core/codemirror/copilot/__tests__/getCodes.test.ts +6 -7
  121. package/src/core/codemirror/copilot/getCodes.ts +2 -2
  122. package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +1 -2
  123. package/src/core/codemirror/lsp/utils.ts +1 -1
  124. package/src/core/documentation/doc-lookup.ts +1 -1
  125. package/src/core/edit-app.tsx +2 -5
  126. package/src/core/errors/error-entries.ts +1 -1
  127. package/src/core/islands/__tests__/bootstrap.test.ts +2 -6
  128. package/src/core/islands/components/web-components.tsx +1 -1
  129. package/src/core/notebook/document/__tests__/codemirror.test.ts +8 -0
  130. package/src/core/session/__tests__/useNotebookSession.hook.test.tsx +1 -1
  131. package/src/stories/cell.stories.tsx +1 -2
  132. package/src/stories/layout/vertical/one-column.stories.tsx +1 -2
  133. package/src/stories/runtime-story.tsx +2 -3
  134. package/src/utils/__tests__/id-tree.test.ts +0 -617
  135. package/src/utils/id-tree.tsx +0 -609
@@ -1,26 +1,34 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- /** Notebook operations that move cells within and across columns. */
3
+ /** Notebook operations that move cells and arrange columns. */
4
4
 
5
5
  import { dequal as isEqual } from "dequal";
6
6
  import { atom, type Getter, type Setter } from "jotai";
7
- import type { CellColumnId, CellIndex } from "@/utils/id-tree";
7
+ import { reorderColumnSizes } from "@/components/editor/columns/storage";
8
+ import type {
9
+ DocumentEditor,
10
+ EditableDocument,
11
+ } from "@/core/notebook/document";
8
12
  import { arrayMove } from "@/utils/arrays";
9
13
  import { notebookAtom, type UndoMoveEntry } from "./cells";
10
14
  import { documentOf, focusWhenMounted, modelOf } from "./edit";
11
- import type { CellId } from "./ids";
15
+ import { CellId } from "./ids";
12
16
  import {
17
+ type CellColumnId,
18
+ columnIdsAtom,
13
19
  columnOf,
20
+ emptyColumnIdsAtom,
14
21
  findColumn,
15
22
  type Layout,
16
23
  type LayoutColumn,
17
24
  layoutAtom,
25
+ newColumnId,
18
26
  } from "./layout";
19
27
 
20
28
  /** A placement to restore a cell to: its column and its index there. */
21
29
  export interface Placement {
22
30
  columnId: CellColumnId;
23
- index: CellIndex;
31
+ index: number;
24
32
  }
25
33
 
26
34
  /** A cell with the cells its fold hides, which move with it. */
@@ -66,12 +74,11 @@ function columnConfigs(
66
74
  return fixes;
67
75
  }
68
76
 
69
- /**
70
- * Make the document's cells fall into `columns`, in that order, as one edit.
71
- * Nothing is written when they already do.
72
- */
73
- export function arrange(get: Getter, set: Setter, columns: CellId[][]): void {
74
- const document = documentOf(get);
77
+ /** The steps that make the document's cells fall into `columns`, or null when they do. */
78
+ function arrangement(
79
+ document: EditableDocument,
80
+ columns: readonly (readonly CellId[])[],
81
+ ): ((tx: DocumentEditor) => void) | null {
75
82
  const order = columns.flat();
76
83
  const fixes = columnConfigs(
77
84
  columns,
@@ -79,16 +86,61 @@ export function arrange(get: Getter, set: Setter, columns: CellId[][]): void {
79
86
  );
80
87
  const reordered = !isEqual(order, document.order);
81
88
  if (!reordered && fixes.length === 0) {
82
- return;
89
+ return null;
83
90
  }
84
- set(modelOf(get).editAtom, (tx) => {
91
+ return (tx) => {
85
92
  if (reordered) {
86
93
  tx.setOrder(order);
87
94
  }
88
95
  for (const [id, column] of fixes) {
89
96
  tx.updateConfig(id, { column });
90
97
  }
91
- });
98
+ };
99
+ }
100
+
101
+ /**
102
+ * How many of `columns` the document holds: all but the trailing empty
103
+ * ones, which the page alone keeps.
104
+ */
105
+ function heldByDocument(columns: readonly (readonly CellId[])[]): number {
106
+ let count = columns.length;
107
+ while (count > 1 && columns[count - 1].length === 0) {
108
+ count -= 1;
109
+ }
110
+ return count;
111
+ }
112
+
113
+ /** Name the page's columns: `count` of `ids` for the document's, the rest kept empty. */
114
+ function nameColumns(
115
+ set: Setter,
116
+ ids: readonly CellColumnId[],
117
+ count: number,
118
+ ): void {
119
+ set(columnIdsAtom, ids.slice(0, count));
120
+ set(emptyColumnIdsAtom, ids.slice(count));
121
+ }
122
+
123
+ /**
124
+ * Make the document's cells fall into `columns`, in that order, as one
125
+ * edit, and name the columns `ids`, by default as they stand. Nothing is
126
+ * written when the cells already fall that way. Trailing columns left
127
+ * empty stay on the page, as the document cannot hold them, until a drop
128
+ * compacts them.
129
+ */
130
+ export function arrange(
131
+ get: Getter,
132
+ set: Setter,
133
+ columns: CellId[][],
134
+ ids: readonly CellColumnId[] = get(layoutAtom).columns.map(
135
+ (column) => column.id,
136
+ ),
137
+ ): void {
138
+ const count = heldByDocument(columns);
139
+ nameColumns(set, ids, count);
140
+ const edit = arrangement(documentOf(get), columns.slice(0, count));
141
+ if (edit !== null) {
142
+ set(modelOf(get).editAtom, edit);
143
+ }
92
144
  }
93
145
 
94
146
  /**
@@ -339,3 +391,144 @@ export function placeCells(
339
391
  }
340
392
  arrange(get, set, cleaned);
341
393
  }
394
+
395
+ // Columns --------------------------------------------------------------------
396
+
397
+ /** Add a column after another, with one new cell to start it, and focus the cell. */
398
+ export const addColumnAtom = atom(
399
+ null,
400
+ (get, set, { columnId }: { columnId: CellColumnId }): void => {
401
+ const layout = get(layoutAtom);
402
+ const at = layout.columns.findIndex((column) => column.id === columnId);
403
+ if (at === -1) {
404
+ return;
405
+ }
406
+ const id = CellId.create();
407
+ const columns = columnsOf(layout);
408
+ columns.splice(at + 1, 0, [id]);
409
+ const ids = layout.columns.map((column) => column.id);
410
+ ids.splice(at + 1, 0, newColumnId());
411
+ const document = documentOf(get);
412
+ const previous = layout.columns[at].ids.at(-1);
413
+ const count = heldByDocument(columns);
414
+ nameColumns(set, ids, count);
415
+ set(modelOf(get).editAtom, (tx) => {
416
+ tx.createCell(
417
+ { id, code: "", config: { column: at + 1 } },
418
+ previous === undefined ? {} : { after: previous },
419
+ );
420
+ arrangement(document, columns.slice(0, count))?.(tx);
421
+ });
422
+ focusWhenMounted(set, id);
423
+ },
424
+ );
425
+
426
+ /** Start a new column at a cell, unless it already starts the first column. */
427
+ export const addColumnBreakpointAtom = atom(
428
+ null,
429
+ (get, set, { cellId }: { cellId: CellId }): void => {
430
+ const layout = get(layoutAtom);
431
+ const column = columnOf(layout, cellId);
432
+ const at = layout.columns.indexOf(column);
433
+ const index = column.ids.indexOf(cellId);
434
+ if (at === 0 && index === 0) {
435
+ return;
436
+ }
437
+ const columns = columnsOf(layout);
438
+ columns.splice(at, 1, column.ids.slice(0, index), column.ids.slice(index));
439
+ const ids = layout.columns.map((column) => column.id);
440
+ // The id stays with the cells, so the half without any is the new one.
441
+ ids.splice(index === 0 ? at : at + 1, 0, newColumnId());
442
+ arrange(get, set, columns, ids);
443
+ },
444
+ );
445
+
446
+ /** Remove a column, moving its cells to the column before it, or after it for the first. */
447
+ export const deleteColumnAtom = atom(
448
+ null,
449
+ (get, set, { columnId }: { columnId: CellColumnId }): void => {
450
+ const layout = get(layoutAtom);
451
+ const at = layout.columns.findIndex((column) => column.id === columnId);
452
+ if (at === -1 || layout.columns.length <= 1) {
453
+ return;
454
+ }
455
+ const columns = columnsOf(layout);
456
+ const target = at === 0 ? 1 : at - 1;
457
+ columns[target] =
458
+ at === 0
459
+ ? [...columns[at], ...columns[target]]
460
+ : [...columns[target], ...columns[at]];
461
+ columns.splice(at, 1);
462
+ const ids = layout.columns.map((column) => column.id);
463
+ ids.splice(at, 1);
464
+ arrange(get, set, columns, ids);
465
+ },
466
+ );
467
+
468
+ /** Bring every cell into the first column. */
469
+ export const mergeAllColumnsAtom = atom(null, (get, set): void => {
470
+ const layout = get(layoutAtom);
471
+ if (layout.columns.length <= 1) {
472
+ return;
473
+ }
474
+ arrange(
475
+ get,
476
+ set,
477
+ [layout.columns.flatMap((column) => [...column.ids])],
478
+ [layout.columns[0].id],
479
+ );
480
+ });
481
+
482
+ /** Drop the columns a drag left empty. */
483
+ export const compactColumnsAtom = atom(null, (get, set): void => {
484
+ const layout = get(layoutAtom);
485
+ const kept = layout.columns.filter((column) => column.ids.length > 0);
486
+ if (kept.length === layout.columns.length || layout.columns.length <= 1) {
487
+ return;
488
+ }
489
+ arrange(
490
+ get,
491
+ set,
492
+ kept.map((column) => [...column.ids]),
493
+ kept.map((column) => column.id),
494
+ );
495
+ });
496
+
497
+ /** Move a column where another is, or one step left or right, with its saved width. */
498
+ export const moveColumnAtom = atom(
499
+ null,
500
+ (
501
+ get,
502
+ set,
503
+ {
504
+ column,
505
+ overColumn,
506
+ }: {
507
+ column: CellColumnId;
508
+ overColumn: CellColumnId | "_left_" | "_right_";
509
+ },
510
+ ): void => {
511
+ if (column === overColumn) {
512
+ return;
513
+ }
514
+ const layout = get(layoutAtom);
515
+ const ids = layout.columns.map((column) => column.id);
516
+ const from = ids.indexOf(column);
517
+ const to =
518
+ overColumn === "_left_"
519
+ ? from - 1
520
+ : overColumn === "_right_"
521
+ ? from + 1
522
+ : ids.indexOf(overColumn);
523
+ if (from === -1 || to < 0 || to >= ids.length) {
524
+ return;
525
+ }
526
+ arrange(
527
+ get,
528
+ set,
529
+ arrayMove(columnsOf(layout), from, to),
530
+ arrayMove(ids, from, to),
531
+ );
532
+ reorderColumnSizes(from, to);
533
+ },
534
+ );
@@ -6,11 +6,7 @@ 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
- type CellColumnId,
11
- type CellIndex,
12
- MultiColumn,
13
- } from "@/utils/id-tree";
9
+ import type { CellColumnId } from "./layout";
14
10
  import { invariant } from "@/utils/invariant";
15
11
  import { Logger } from "@/utils/Logger";
16
12
  import { Objects } from "@/utils/objects";
@@ -18,8 +14,6 @@ import { extractAllTracebackInfo, type TracebackInfo } from "@/utils/traceback";
18
14
  import { createReducerAndAtoms } from "../../utils/createReducer";
19
15
  import { foldAllBulk, unfoldAllBulk } from "../codemirror/editing/commands";
20
16
  import { editorMountScheduler } from "../codemirror/editor-mount-scheduler";
21
- import { prepareCellCodeUpdate } from "../codemirror/language/cell-code";
22
- import { splitEditor } from "../codemirror/language/utils";
23
17
  import type { SerializedEditorState } from "../codemirror/types";
24
18
  import { mergeOutlines } from "../dom/outline";
25
19
  import type { CellMessage } from "../kernel/messages";
@@ -30,7 +24,7 @@ import { isWasm } from "../wasm/utils";
30
24
  import { prepareCellForExecution, transitionCell } from "./cell";
31
25
  import { applyDocument, documentEditMiddleware } from "./document";
32
26
  import { isCodeHidden, shownHiddenCodeAtom } from "./editor";
33
- import { CellId, SCRATCH_CELL_ID } from "./ids";
27
+ import { type CellId, SCRATCH_CELL_ID } from "./ids";
34
28
  import { type CellLog, getCellLogsForMessage } from "./logs";
35
29
  import type { Outline } from "./outline";
36
30
  import { focusAndScrollCellIntoView } from "./scrollCellIntoView";
@@ -74,7 +68,7 @@ export interface UndoDeleteEntry {
74
68
  export interface UndoMoveEntry {
75
69
  type: "move";
76
70
  cellIds: CellId[];
77
- placements: Array<{ columnId: CellColumnId; index: CellIndex }>;
71
+ placements: Array<{ columnId: CellColumnId; index: number }>;
78
72
  }
79
73
 
80
74
  export type HistoryEntry = UndoDeleteEntry | UndoMoveEntry;
@@ -86,7 +80,8 @@ export interface NotebookState {
86
80
  /**
87
81
  * Order of cells on the page.
88
82
  */
89
- cellIds: MultiColumn<CellId>;
83
+ /** The cells in document order. */
84
+ cellIds: CellId[];
90
85
  /**
91
86
  * Map of cells to their view state
92
87
  */
@@ -144,7 +139,7 @@ function withScratchCell(notebookState: NotebookState): NotebookState {
144
139
  */
145
140
  export function initialNotebookState(): NotebookState {
146
141
  return withScratchCell({
147
- cellIds: MultiColumn.from([]),
142
+ cellIds: [],
148
143
  cellData: {},
149
144
  cellRuntime: {},
150
145
  cellHandles: {},
@@ -164,74 +159,6 @@ const {
164
159
  useActions,
165
160
  valueAtom: notebookAtom,
166
161
  } = createReducerAndAtoms(initialNotebookState, {
167
- moveColumn: (
168
- state,
169
- action: {
170
- column: CellColumnId;
171
- overColumn: CellColumnId | "_left_" | "_right_";
172
- },
173
- ) => {
174
- if (action.column === action.overColumn) {
175
- return state;
176
- }
177
- return {
178
- ...state,
179
- cellIds: state.cellIds.moveColumn(action.column, action.overColumn),
180
- };
181
- },
182
- addColumn: (state, action: { columnId: CellColumnId }) => {
183
- // Add column and new cell
184
- const newCellId = CellId.create();
185
- return {
186
- ...state,
187
- cellIds: state.cellIds.addColumn(action.columnId, [newCellId]),
188
- cellData: {
189
- ...state.cellData,
190
- [newCellId]: createCell({
191
- id: newCellId,
192
- }),
193
- },
194
- cellRuntime: {
195
- ...state.cellRuntime,
196
- [newCellId]: createCellRuntimeState(),
197
- },
198
- cellHandles: {
199
- ...state.cellHandles,
200
- [newCellId]: createRef(),
201
- },
202
- scrollKey: newCellId,
203
- };
204
- },
205
- addColumnBreakpoint: (state, action: { cellId: CellId }) => {
206
- const { cellId } = action;
207
- if (state.cellIds.getColumns()[0].inOrderIds[0] === cellId) {
208
- return state;
209
- }
210
- return {
211
- ...state,
212
- cellIds: state.cellIds.insertBreakpoint(cellId),
213
- };
214
- },
215
- deleteColumn: (state, action: { columnId: CellColumnId }) => {
216
- // Move all cells in the column to the previous column
217
- const { columnId } = action;
218
- return {
219
- ...state,
220
- cellIds: state.cellIds.delete(columnId),
221
- };
222
- },
223
- mergeAllColumns: (state) => {
224
- return {
225
- ...state,
226
- cellIds: state.cellIds.mergeAllColumns(),
227
- };
228
- },
229
- compactColumns: (state) => {
230
- return {
231
- ...state,
232
- cellIds: state.cellIds.compact(),
233
- };
234
- },
235
162
  /**
236
163
  * Put moved cells back where they were.
237
164
  *
@@ -332,7 +259,7 @@ const {
332
259
  /** Follow the host document's cells and order. */
333
260
  reconcileDocument: (state, document: EditableDocument) => {
334
261
  const nextState = applyDocument(state, document);
335
- for (const cellId of state.cellIds.inOrderIds) {
262
+ for (const cellId of state.cellIds) {
336
263
  if (!(cellId in nextState.cellData)) {
337
264
  releaseCellAtoms(cellId);
338
265
  }
@@ -382,7 +309,7 @@ const {
382
309
  },
383
310
  ) => {
384
311
  const cellData = { ...state.cellData };
385
- for (const cellId of state.cellIds.inOrderIds) {
312
+ for (const cellId of state.cellIds) {
386
313
  const cell = state.cellData[cellId];
387
314
  const lastCodeRun = action.lastCodeRun[cellId] ?? null;
388
315
  cellData[cellId] = {
@@ -402,7 +329,7 @@ const {
402
329
  resetCellRuntimes: (state) => {
403
330
  const preserveSnapshot = isWasm();
404
331
  const cellRuntime = { ...state.cellRuntime };
405
- for (const cellId of [...state.cellIds.inOrderIds, SCRATCH_CELL_ID]) {
332
+ for (const cellId of [...state.cellIds, SCRATCH_CELL_ID]) {
406
333
  const previous = state.cellRuntime[cellId];
407
334
  const hasSnapshot =
408
335
  preserveSnapshot &&
@@ -419,12 +346,7 @@ const {
419
346
  return state;
420
347
  }
421
348
 
422
- const column = state.cellIds.findWithId(scrollKey);
423
- const index = column.indexOfOrThrow(scrollKey);
424
-
425
- const cellId =
426
- index === column.length - 1 ? column.last() : column.atOrThrow(index);
427
-
349
+ const cellId = scrollKey;
428
350
  focusAndScrollCellIntoView({
429
351
  cellId: cellId,
430
352
  cell: state.cellHandles[cellId],
@@ -462,110 +384,6 @@ const {
462
384
  cellLogs: [],
463
385
  };
464
386
  },
465
- splitCell: (state, action: { cellId: CellId }) => {
466
- const { cellId } = action;
467
- const column = state.cellIds.findWithId(cellId);
468
- const index = column.indexOfOrThrow(cellId);
469
- const cell = state.cellData[cellId];
470
- const cellHandle = state.cellHandles[cellId].current;
471
-
472
- if (cellHandle?.editorView == null) {
473
- return state;
474
- }
475
-
476
- const { beforeCursorCode, afterCursorCode } = splitEditor(
477
- cellHandle.editorView,
478
- );
479
-
480
- const update = prepareCellCodeUpdate(
481
- cellHandle.editorView.state,
482
- beforeCursorCode,
483
- );
484
-
485
- if (update) {
486
- cellHandle.editorView.dispatch(update);
487
- }
488
-
489
- const newCellId = CellId.create();
490
-
491
- return {
492
- ...state,
493
- cellIds: state.cellIds.insertId(newCellId, column.id, index + 1),
494
- cellData: {
495
- ...state.cellData,
496
- [cellId]: {
497
- ...cell,
498
- code: beforeCursorCode,
499
- edited:
500
- Boolean(beforeCursorCode) &&
501
- beforeCursorCode.trim() !== cell.lastCodeRun?.trim(),
502
- },
503
- [newCellId]: createCell({
504
- id: newCellId,
505
- code: afterCursorCode,
506
- edited: Boolean(afterCursorCode),
507
- }),
508
- },
509
- cellRuntime: {
510
- ...state.cellRuntime,
511
- [cellId]: {
512
- ...state.cellRuntime[cellId],
513
- output: null,
514
- consoleOutputs: [],
515
- },
516
- [newCellId]: createCellRuntimeState(),
517
- },
518
- cellHandles: {
519
- ...state.cellHandles,
520
- [newCellId]: createRef(),
521
- },
522
- scrollKey: newCellId,
523
- };
524
- },
525
- undoSplitCell: (state, action: { cellId: CellId; snapshot: string }) => {
526
- const { cellId, snapshot } = action;
527
-
528
- const cell = state.cellData[cellId];
529
- const cellHandle = state.cellHandles[cellId].current;
530
-
531
- if (cellHandle?.editorView == null) {
532
- return state;
533
- }
534
-
535
- const update = prepareCellCodeUpdate(cellHandle.editorView.state, snapshot);
536
-
537
- if (update) {
538
- cellHandle.editorView.dispatch(update);
539
- }
540
-
541
- return {
542
- ...state,
543
- cellIds: state.cellIds.transformWithCellId(cellId, (column) => {
544
- const newCellIndex = column.indexOfOrThrow(cellId) + 1;
545
- return column.deleteAtIndex(newCellIndex);
546
- }),
547
- cellData: {
548
- ...state.cellData,
549
- [cellId]: {
550
- ...cell,
551
- code: snapshot,
552
- edited:
553
- Boolean(snapshot) && snapshot?.trim() !== cell.lastCodeRun?.trim(),
554
- },
555
- },
556
- cellRuntime: {
557
- ...state.cellRuntime,
558
- [cellId]: {
559
- ...state.cellRuntime[cellId],
560
- output: null,
561
- consoleOutputs: [],
562
- },
563
- },
564
- cellHandles: {
565
- ...state.cellHandles,
566
- },
567
- };
568
- },
569
387
  clearCellOutput: (state, action: { cellId: CellId }) => {
570
388
  const { cellId } = action;
571
389
  return updateCellRuntimeState({
@@ -594,7 +412,7 @@ const {
594
412
  },
595
413
  clearAllCellOutputs: (state) => {
596
414
  const newCellRuntime = { ...state.cellRuntime };
597
- for (const cellId of state.cellIds.inOrderIds) {
415
+ for (const cellId of state.cellIds) {
598
416
  newCellRuntime[cellId] = {
599
417
  ...newCellRuntime[cellId],
600
418
  output: null,
@@ -660,43 +478,12 @@ function updateCellData({
660
478
  };
661
479
  }
662
480
 
481
+ /** Each cell's config in order. The document's `column` configs are canonical. */
663
482
  export function getCellConfigs(state: NotebookState): CellConfig[] {
664
- const cells = state.cellData;
665
-
666
- // We set to null by default to prevent inconsistencies between undefined & null
667
- const defaultCellConfig: Partial<CellConfig> = { column: null };
668
-
669
- // Handle the case where there's only one column
670
- // We don't want to set the column config
671
- const hasMultipleColumns = state.cellIds.getColumns().length > 1;
672
- if (!hasMultipleColumns) {
673
- return state.cellIds.getColumns().flatMap((column) => {
674
- return column.inOrderIds.map((cellId) => {
675
- return {
676
- ...cells[cellId].config,
677
- ...defaultCellConfig,
678
- };
679
- });
680
- });
681
- }
682
-
683
- return state.cellIds.getColumns().flatMap((column, columnIndex) => {
684
- return column.inOrderIds.map((cellId, cellIndex) => {
685
- const config: Partial<CellConfig> = { ...defaultCellConfig };
686
-
687
- // Only set the column index for the first cell in the column
688
- if (cellIndex === 0) {
689
- config.column = columnIndex;
690
- }
691
-
692
- const newConfig = {
693
- ...cells[cellId].config,
694
- ...config,
695
- };
696
-
697
- return newConfig;
698
- });
699
- });
483
+ return state.cellIds.map((cellId) => ({
484
+ ...state.cellData[cellId].config,
485
+ column: state.cellData[cellId].config.column ?? null,
486
+ }));
700
487
  }
701
488
 
702
489
  export {
@@ -716,9 +503,7 @@ export const addLogs = imperativeActions.addLogs;
716
503
 
717
504
  export const cellIdsAtom = atom((get) => get(notebookAtom).cellIds);
718
505
 
719
- export const hasOnlyOneCellAtom = atom((get) =>
720
- get(cellIdsAtom).hasOnlyOneId(),
721
- );
506
+ export const hasOnlyOneCellAtom = atom((get) => get(cellIdsAtom).length === 1);
722
507
 
723
508
  export const hasDisabledCellsAtom = atom(
724
509
  (get) => disabledCellIds(get(notebookAtom)).length > 0,
@@ -736,7 +521,7 @@ export const needsRunAtom = atom((get) => notebookNeedsRun(get(notebookAtom)));
736
521
 
737
522
  export const cellErrorsAtom = atom((get) => {
738
523
  const { cellIds, cellRuntime, cellData } = get(notebookAtom);
739
- const errors = cellIds.inOrderIds
524
+ const errors = cellIds
740
525
  .map((cellId) => {
741
526
  const cell = cellRuntime[cellId];
742
527
  const { name } = cellData[cellId];
@@ -785,9 +570,7 @@ export const cellOutlinesAtom = createDeepEqualAtom(
785
570
  export const notebookOutline = atom((get) => {
786
571
  const cellIds = get(cellIdsAtom);
787
572
  const cellRuntime = get(cellsRuntimeAtom);
788
- const outlines = cellIds.inOrderIds.map(
789
- (cellId) => cellRuntime[cellId].outline,
790
- );
573
+ const outlines = cellIds.map((cellId) => cellRuntime[cellId].outline);
791
574
  return mergeOutlines(outlines);
792
575
  });
793
576
 
@@ -797,7 +580,7 @@ export const cellIdToNamesMap = createDeepEqualAtom(
797
580
  atom((get) => {
798
581
  const { cellIds, cellData } = get(notebookAtom);
799
582
  const names: Record<CellId, string | undefined> = Objects.fromEntries(
800
- cellIds.inOrderIds.map((cellId) => [cellId, cellData[cellId]?.name]),
583
+ cellIds.map((cellId) => [cellId, cellData[cellId]?.name]),
801
584
  );
802
585
  return names;
803
586
  }),
@@ -854,12 +637,12 @@ export const getCells = () => store.get(notebookAtom).cellIds;
854
637
  */
855
638
  export const getCellNames = () => {
856
639
  const { cellIds, cellData } = store.get(notebookAtom);
857
- return cellIds.inOrderIds.map((id) => cellData[id]?.name).filter(Boolean);
640
+ return cellIds.map((id) => cellData[id]?.name).filter(Boolean);
858
641
  };
859
642
 
860
643
  const cellDataAtoms = splitAtom(
861
644
  selectAtom(notebookAtom, (cells) =>
862
- cells.cellIds.inOrderIds.map((id) => cells.cellData[id]),
645
+ cells.cellIds.map((id) => cells.cellData[id]),
863
646
  ),
864
647
  );
865
648
  export const useCellDataAtoms = () => useAtom(cellDataAtoms);
@@ -879,16 +662,7 @@ export const notebookQueuedOrRunningCountAtom = atom((get) =>
879
662
  notebookQueueOrRunningCount(get(notebookAtom)),
880
663
  );
881
664
 
882
- export const numColumnsAtom = atom(
883
- (get) => get(notebookAtom).cellIds.colLength,
884
- );
885
- export const hasCellsAtom = atom(
886
- (get) => get(notebookAtom).cellIds.idLength > 0,
887
- );
888
- export const columnIdsAtom = atom((get) =>
889
- get(notebookAtom).cellIds.getColumnIds(),
890
- );
891
-
665
+ export const hasCellsAtom = atom((get) => get(notebookAtom).cellIds.length > 0);
892
666
  export const cellDataAtom = atomFamily((cellId: CellId) =>
893
667
  atom((get) => get(notebookAtom).cellData[cellId]),
894
668
  );
@@ -929,7 +703,7 @@ export const useCellHandle = (cellId: CellId) =>
929
703
  */
930
704
  export const getAllEditorViews = () => {
931
705
  const { cellIds, cellHandles } = store.get(notebookAtom);
932
- return cellIds.inOrderIds
706
+ return cellIds
933
707
  .map((cellId) => cellHandles[cellId]?.current?.editorViewOrNull)
934
708
  .filter(Boolean);
935
709
  };
@@ -953,12 +727,10 @@ export function flattenTopLevelNotebookCells(
953
727
  state: NotebookState,
954
728
  ): (CellData & CellRuntimeState)[] {
955
729
  const { cellIds, cellData, cellRuntime } = state;
956
- return cellIds.getColumns().flatMap((column) =>
957
- column.topLevelIds.map((cellId) => ({
958
- ...cellData[cellId],
959
- ...cellRuntime[cellId],
960
- })),
961
- );
730
+ return cellIds.map((cellId) => ({
731
+ ...cellData[cellId],
732
+ ...cellRuntime[cellId],
733
+ }));
962
734
  }
963
735
 
964
736
  export function createTracebackInfoAtom(