@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
@@ -2,10 +2,9 @@
2
2
 
3
3
  import { useDeleteCellCallback } from "@/components/editor/cell/useDeleteCell";
4
4
  import { outputIsStale } from "@/core/cells/cell";
5
- import { useCellIds } from "@/core/cells/cells";
6
5
  import { useCellActions } from "@/core/cells/actions";
7
6
  import type { CellId } from "@/core/cells/ids";
8
- import type { CellColumnId } from "@/utils/id-tree";
7
+ import { type CellColumnId, type Layout, useLayout } from "@/core/cells/layout";
9
8
  import { useEffect, useRef, useState } from "react";
10
9
  import type { ICellRendererProps } from "../editor/renderers/types";
11
10
  import type {
@@ -50,7 +49,6 @@ import { SLIDE_TYPE_OPTIONS_BY_VALUE } from "./slide-form";
50
49
 
51
50
  type Props = ICellRendererProps<SlidesLayout>;
52
51
  type SlideCell = Props["cells"][number];
53
- type CellIdsState = ReturnType<typeof useCellIds>;
54
52
  type DropPosition = "before" | "after";
55
53
 
56
54
  interface ProjectedDropTarget {
@@ -229,7 +227,7 @@ export const SlidesMinimap = ({
229
227
  slideTypes,
230
228
  onSlideClick,
231
229
  }: SlidesMinimapProps) => {
232
- const cellIds = useCellIds();
230
+ const layout = useLayout();
233
231
  const { moveCellToIndex, createCell } = useCellActions();
234
232
  const deleteCell = useDeleteCellCallback();
235
233
  const containerRef = useRef<HTMLDivElement>(null);
@@ -297,7 +295,7 @@ export const SlidesMinimap = ({
297
295
  try {
298
296
  if (activeId && dropTarget) {
299
297
  const resolvedTarget = resolveDropTarget({
300
- cellIds,
298
+ layout,
301
299
  activeId,
302
300
  target: dropTarget,
303
301
  });
@@ -751,24 +749,27 @@ function projectDropTarget(
751
749
  }
752
750
 
753
751
  function resolveDropTarget({
754
- cellIds,
752
+ layout,
755
753
  activeId,
756
754
  target,
757
755
  }: {
758
- cellIds: CellIdsState;
756
+ layout: Layout;
759
757
  activeId: CellId;
760
758
  target: ProjectedDropTarget;
761
759
  }): ResolvedDropTarget | null {
762
760
  if (activeId === target.overId) {
763
761
  return null;
764
762
  }
765
- if (cellIds.colLength !== 1) {
763
+ if (layout.columns.length !== 1) {
766
764
  Logger.warn("Multi-column mode is not supported");
767
765
  return null;
768
766
  }
769
767
 
770
- const column = cellIds.findWithId(target.overId);
771
- const overIndex = column.indexOfOrThrow(target.overId);
768
+ const [column] = layout.columns;
769
+ const overIndex = column.ids.indexOf(target.overId);
770
+ if (overIndex === -1) {
771
+ return null;
772
+ }
772
773
 
773
774
  return {
774
775
  cellId: activeId,
@@ -22,10 +22,8 @@ import React, { useCallback, useMemo, useState } from "react";
22
22
  import type { CellId } from "@/core/cells/ids";
23
23
  import { useAppConfig } from "@/core/config/config";
24
24
  import { Arrays } from "@/utils/arrays";
25
- import type { CellColumnId, MultiColumn } from "@/utils/id-tree";
26
25
  import { invariant } from "@/utils/invariant";
27
- import { getNotebook } from "../../core/cells/cells";
28
- import { findColumn, layoutAtom } from "../../core/cells/layout";
26
+ import { findColumn, isColumnId, layoutAtom } from "../../core/cells/layout";
29
27
  import { store } from "../../core/state/jotai";
30
28
  import { useCellActions } from "../../core/cells/actions";
31
29
  import { useEvent } from "../../hooks/useEvent";
@@ -50,9 +48,6 @@ const SortableCellsProviderInternal = ({
50
48
  useCellActions();
51
49
 
52
50
  const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null);
53
- const [clonedItems, setClonedItems] = useState<MultiColumn<CellId> | null>(
54
- null,
55
- );
56
51
 
57
52
  const config = useAppConfig();
58
53
  const modifiers = useMemo(() => {
@@ -76,20 +71,10 @@ const SortableCellsProviderInternal = ({
76
71
 
77
72
  const handleDragStart = useEvent((event: DragStartEvent) => {
78
73
  setActiveId(event.active.id);
79
- const notebook = getNotebook();
80
- setClonedItems(notebook.cellIds);
81
74
  });
82
75
 
83
76
  const handleDragCancel = useEvent(() => {
84
- // TODO: restore cloned items
85
- if (clonedItems) {
86
- // Reset items to their original state in case items have been
87
- // Dragged across containers
88
- // setItems(clonedItems);
89
- }
90
-
91
77
  setActiveId(null);
92
- setClonedItems(null);
93
78
  });
94
79
 
95
80
  /**
@@ -236,9 +221,5 @@ const SortableCellsProviderInternal = ({
236
221
  export const SortableCellsProvider = React.memo(SortableCellsProviderInternal);
237
222
 
238
223
  function isCellId(id: UniqueIdentifier): id is CellId {
239
- return typeof id === "string" && !id.startsWith("tree_");
240
- }
241
-
242
- function isColumnId(id: UniqueIdentifier): id is CellColumnId {
243
- return typeof id === "string" && id.startsWith("tree_");
224
+ return typeof id === "string" && !isColumnId(id);
244
225
  }
@@ -209,7 +209,7 @@ const TraceBlockBody: React.FC<{
209
209
  const elapsedTime = cellRun.elapsedTime ?? 0;
210
210
  return {
211
211
  cell: cellRun.cellId,
212
- cellNum: cellIds.inOrderIds.indexOf(cellRun.cellId),
212
+ cellNum: cellIds.indexOf(cellRun.cellId),
213
213
  startTimestamp: formatChartTime(cellRun.startTime),
214
214
  endTimestamp: formatChartTime(cellRun.startTime + elapsedTime),
215
215
  elapsedTime: formatElapsedTime(elapsedTime * 1000),
@@ -43,7 +43,7 @@ vi.mock("../../cells/actions", () => ({
43
43
  vi.mock("../../cells/cells", async () => {
44
44
  const { atom } = await import("jotai");
45
45
  return {
46
- notebookAtom: atom({ cellData: {}, cellIds: { inOrderIds: [] } }),
46
+ notebookAtom: atom({ cellData: {}, cellIds: [] }),
47
47
  cellHandleAtom: vi.fn(() => ({
48
48
  read: vi.fn(() => mockCellHandle),
49
49
  })),
@@ -34,9 +34,7 @@ describe("CellOutputContextProvider", () => {
34
34
  beforeEach(() => {
35
35
  // Create a basic mock notebook state
36
36
  mockNotebook = {
37
- cellIds: {
38
- inOrderIds: ["cell1", "cell2", "cell3"],
39
- },
37
+ cellIds: ["cell1", "cell2", "cell3"],
40
38
  cellData: {
41
39
  cell1: {
42
40
  id: "cell1",
@@ -207,7 +205,7 @@ describe("Cell output utility functions", () => {
207
205
 
208
206
  beforeEach(() => {
209
207
  const mockNotebook = {
210
- cellIds: { inOrderIds: [] },
208
+ cellIds: [],
211
209
  cellData: {},
212
210
  cellRuntime: {},
213
211
  } as unknown as NotebookState;
@@ -226,7 +224,7 @@ describe("Cell output utility functions", () => {
226
224
 
227
225
  for (const testCase of testCases) {
228
226
  mockStore = createMockStore({
229
- cellIds: { inOrderIds: ["test"] },
227
+ cellIds: ["test"],
230
228
  cellData: {
231
229
  test: { id: "test", name: "", code: "" },
232
230
  },
@@ -261,7 +259,7 @@ describe("Cell output utility functions", () => {
261
259
 
262
260
  for (const testCase of testCases) {
263
261
  mockStore = createMockStore({
264
- cellIds: { inOrderIds: ["test"] },
262
+ cellIds: ["test"],
265
263
  cellData: {
266
264
  test: { id: "test", name: "", code: "" },
267
265
  },
@@ -295,7 +293,7 @@ describe("Cell output utility functions", () => {
295
293
 
296
294
  for (const testCase of testCases) {
297
295
  mockStore = createMockStore({
298
- cellIds: { inOrderIds: ["test"] },
296
+ cellIds: ["test"],
299
297
  cellData: {
300
298
  test: { id: "test", name: "", code: "" },
301
299
  },
@@ -338,7 +336,7 @@ describe("Cell output utility functions", () => {
338
336
  } as unknown as Document;
339
337
 
340
338
  const mockNotebook = {
341
- cellIds: { inOrderIds: [] },
339
+ cellIds: [],
342
340
  cellData: {},
343
341
  cellRuntime: {},
344
342
  } as unknown as NotebookState;
@@ -369,7 +367,7 @@ describe("Cell output utility functions", () => {
369
367
  ).mockReturnValue(mockDiv);
370
368
 
371
369
  mockStore = createMockStore({
372
- cellIds: { inOrderIds: ["test"] },
370
+ cellIds: ["test"],
373
371
  cellData: {
374
372
  test: { id: "test", name: "", code: "" },
375
373
  },
@@ -395,9 +393,7 @@ describe("Cell output utility functions", () => {
395
393
 
396
394
  beforeEach(() => {
397
395
  mockNotebook = {
398
- cellIds: {
399
- inOrderIds: ["cell1", "cell2"],
400
- },
396
+ cellIds: ["cell1", "cell2"],
401
397
  cellData: {
402
398
  cell1: {
403
399
  id: "cell1",
@@ -93,7 +93,7 @@ export class CellOutputContextProvider extends AIContextProvider<CellOutputConte
93
93
  const notebook = this.store.get(notebookAtom);
94
94
  const items: CellOutputContextItem[] = [];
95
95
 
96
- for (const cellId of notebook.cellIds.inOrderIds) {
96
+ for (const cellId of notebook.cellIds) {
97
97
  const cellContextData = getCellContextData(cellId, notebook);
98
98
  const cellOutput = cellContextData.cellOutput;
99
99
 
@@ -358,7 +358,7 @@ export function getCellContextData(
358
358
  const cellRuntime = notebook.cellRuntime[cellId];
359
359
 
360
360
  const cellData = notebook.cellData[cellId];
361
- const cellIndex = notebook.cellIds.inOrderIds.indexOf(cellId);
361
+ const cellIndex = notebook.cellIds.indexOf(cellId);
362
362
  const cellName = displayCellName(cellData.name, cellIndex);
363
363
 
364
364
  let consoleOutputs: BaseOutput[] | undefined;
@@ -4,12 +4,18 @@ import { EditorState } from "@codemirror/state";
4
4
  import { EditorView } from "@codemirror/view";
5
5
  import { getDefaultStore } from "jotai";
6
6
  import { beforeEach, describe, expect, it, vi } from "vitest";
7
+ import { mockDocumentModel } from "@/__mocks__/document";
7
8
  import { MockNotebook } from "@/__mocks__/notebook";
8
9
  import { cellId } from "@/__tests__/branded";
9
10
  import { notebookAtom } from "@/core/cells/cells";
10
11
  import type { CellId } from "@/core/cells/ids";
11
12
  import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
12
- import { MultiColumn } from "@/utils/id-tree";
13
+ import {
14
+ cell,
15
+ CONFIG,
16
+ document as documentOf,
17
+ withCells,
18
+ } from "@/core/notebook/document/__tests__/fixtures";
13
19
  import { cellConfigExtension } from "../../../codemirror/config/extension";
14
20
  import { adaptiveLanguageConfiguration } from "../../../codemirror/language/extension";
15
21
  import { stagedAICellsAtom } from "../../staged-cells";
@@ -61,7 +67,6 @@ describe("EditNotebookTool", () => {
61
67
  let tool: EditNotebookTool;
62
68
  let cellId1: CellId;
63
69
  let cellId2: CellId;
64
- let cellId3: CellId;
65
70
  let toolContext: {
66
71
  addStagedCell: ReturnType<typeof vi.fn>;
67
72
  createCell: ReturnType<typeof vi.fn>;
@@ -86,7 +91,6 @@ describe("EditNotebookTool", () => {
86
91
 
87
92
  cellId1 = cellId("cell-1");
88
93
  cellId2 = cellId("cell-2");
89
- cellId3 = cellId("cell-3");
90
94
 
91
95
  // Reset mocks
92
96
  vi.clearAllMocks();
@@ -443,27 +447,37 @@ describe("EditNotebookTool", () => {
443
447
  [cellId2]: { code: "x = 2" },
444
448
  },
445
449
  });
446
- // Create multi-column layout
447
- notebook.cellIds = MultiColumn.from([[cellId1], [cellId2]]);
448
450
  store.set(notebookAtom, notebook);
449
-
450
- const newCode = "y = 2";
451
- const result = await tool.handler(
452
- {
453
- edit: {
454
- type: "add_cell",
455
- position: { type: "column_end", columnIndex: 1 },
456
- code: newCode,
457
- },
458
- },
459
- toolContext as never,
451
+ // The layout reads columns from the document's cells.
452
+ const { stop } = mockDocumentModel(
453
+ store,
454
+ withCells(documentOf(), {
455
+ c1: null,
456
+ c2: null,
457
+ [cellId1]: cell(cellId1, "x = 1"),
458
+ [cellId2]: cell(cellId2, "x = 2", {
459
+ config: { ...CONFIG, column: 1 },
460
+ }),
461
+ }),
460
462
  );
461
463
 
462
- expect(result.status).toBe("success");
464
+ try {
465
+ const result = await tool.handler(
466
+ {
467
+ edit: {
468
+ type: "add_cell",
469
+ position: { type: "column_end", columnIndex: 1 },
470
+ code: "y = 2",
471
+ },
472
+ },
473
+ toolContext as never,
474
+ );
463
475
 
464
- // Check that a new cell was staged
465
- const stagedCells = store.get(stagedAICellsAtom);
466
- expect(stagedCells.size).toBe(1);
476
+ expect(result.status).toBe("success");
477
+ expect(store.get(stagedAICellsAtom).size).toBe(1);
478
+ } finally {
479
+ stop();
480
+ }
467
481
  });
468
482
 
469
483
  it("should throw error when cell ID doesn't exist for position", async () => {
@@ -698,52 +712,6 @@ describe("EditNotebookTool", () => {
698
712
  });
699
713
  });
700
714
 
701
- describe("validation", () => {
702
- it("should validate cell exists in multi-column notebook", async () => {
703
- const notebook = MockNotebook.notebookState({
704
- cellData: {
705
- [cellId1]: { code: "x = 1" },
706
- [cellId2]: { code: "x = 2" },
707
- [cellId3]: { code: "x = 3" },
708
- },
709
- });
710
- // Create multi-column layout
711
- notebook.cellIds = MultiColumn.from([[cellId1, cellId2], [cellId3]]);
712
- store.set(notebookAtom, notebook);
713
-
714
- // Should not throw for cells in different columns
715
- const editorView = createMockEditorView("x = 1");
716
- notebook.cellHandles[cellId1] = { current: { editorView } } as never;
717
- notebook.cellHandles[cellId3] = { current: { editorView } } as never;
718
-
719
- await expect(
720
- tool.handler(
721
- {
722
- edit: {
723
- type: "update_cell",
724
- position: { cellId: cellId1 },
725
- code: "y = 1",
726
- },
727
- },
728
- toolContext as never,
729
- ),
730
- ).resolves.toBeDefined();
731
-
732
- await expect(
733
- tool.handler(
734
- {
735
- edit: {
736
- type: "update_cell",
737
- position: { cellId: cellId3 },
738
- code: "y = 3",
739
- },
740
- },
741
- toolContext as never,
742
- ),
743
- ).resolves.toBeDefined();
744
- });
745
- });
746
-
747
715
  describe("return value", () => {
748
716
  it("should return success status with next steps", async () => {
749
717
  const notebook = MockNotebook.notebookState({
@@ -7,7 +7,11 @@ import { type NotebookState, notebookAtom } from "@/core/cells/cells";
7
7
  import type { CreateCellOptions } from "@/core/cells/actions";
8
8
  import { CellId } from "@/core/cells/ids";
9
9
  import { prepareCellCodeUpdate } from "@/core/codemirror/language/cell-code";
10
- import type { CellColumnId } from "@/utils/id-tree";
10
+ import {
11
+ type CellColumnId,
12
+ type Layout,
13
+ layoutAtom,
14
+ } from "@/core/cells/layout";
11
15
  import { stagedAICellsAtom } from "../staged-cells";
12
16
  import {
13
17
  type AiTool,
@@ -201,7 +205,10 @@ export class EditNotebookTool implements AiTool<
201
205
  }
202
206
 
203
207
  placement = {
204
- endOf: this.getColumnId(position.columnIndex, notebook),
208
+ endOf: this.getColumnId(
209
+ position.columnIndex,
210
+ store.get(layoutAtom),
211
+ ),
205
212
  };
206
213
  break;
207
214
 
@@ -259,8 +266,7 @@ export class EditNotebookTool implements AiTool<
259
266
  };
260
267
 
261
268
  private validateCellIdExists(cellId: CellId, notebook: NotebookState) {
262
- const cellIds = notebook.cellIds;
263
- if (!cellIds.getColumns().some((column) => column.idSet.has(cellId))) {
269
+ if (!notebook.cellIds.includes(cellId)) {
264
270
  throw new ToolExecutionError("Cell not found", {
265
271
  code: "CELL_NOT_FOUND",
266
272
  isRetryable: false,
@@ -269,12 +275,8 @@ export class EditNotebookTool implements AiTool<
269
275
  }
270
276
  }
271
277
 
272
- private getColumnId(
273
- columnIndex: number,
274
- notebook: NotebookState,
275
- ): CellColumnId {
276
- const cellIds = notebook.cellIds;
277
- const columns = cellIds.getColumns();
278
+ private getColumnId(columnIndex: number, layout: Layout): CellColumnId {
279
+ const { columns } = layout;
278
280
 
279
281
  if (columnIndex < 0 || columnIndex >= columns.length) {
280
282
  throw new ToolExecutionError("Column index is out of range", {
@@ -5,7 +5,6 @@ import { afterEach, describe, expect, it, vi } from "vitest";
5
5
  import { cellId } from "@/__tests__/branded";
6
6
  import { createStore } from "jotai";
7
7
  import { cell, CONFIG } from "@/core/notebook/document/__tests__/fixtures";
8
- import type { CellIndex } from "@/utils/id-tree";
9
8
  import {
10
9
  addSetupCellAtom,
11
10
  collapseAllCellsAtom,
@@ -17,9 +16,11 @@ import {
17
16
  focusCellAtom,
18
17
  moveToNextCellAtom,
19
18
  showCellIfHiddenAtom,
19
+ splitCellAtom,
20
20
  undoDeleteCellAtom,
21
+ undoSplitCellAtom,
21
22
  } from "../actions";
22
- import { moveCellsRelativeToAtom } from "../arrange";
23
+ import { moveCellAtom, moveCellsRelativeToAtom } from "../arrange";
23
24
  import {
24
25
  cellDataAtom,
25
26
  cellHandleAtom,
@@ -95,7 +96,7 @@ describe("createCellAtom", () => {
95
96
  });
96
97
 
97
98
  it("names the column of a cell placed first in a later column", () => {
98
- const { store, notebook, cellIn } = setup({
99
+ const { store, cellIn } = setup({
99
100
  c2: cell("c2", "y = 2", { config: { ...CONFIG, column: 1 } }),
100
101
  });
101
102
 
@@ -103,12 +104,14 @@ describe("createCellAtom", () => {
103
104
 
104
105
  expect(cellIn(id as string)?.config.column).toBe(1);
105
106
  expect(cellIn(c2)?.config.column).toBeNull();
106
- expect(notebook().cellIds.atOrThrow(1).inOrderIds).toEqual([id, c2]);
107
- expect(notebook().cellIds.atOrThrow(0).inOrderIds).toEqual([c1]);
107
+ expect(store.get(layoutAtom).columns.map((c) => c.ids)).toEqual([
108
+ [c1],
109
+ [id, c2],
110
+ ]);
108
111
  });
109
112
 
110
113
  it("places a cell in the column of its anchor, whatever config it carries", () => {
111
- const { store, order, notebook, cellIn } = setup({
114
+ const { store, order, cellIn } = setup({
112
115
  c2: cell("c2", "y = 2", { config: { ...CONFIG, column: 1 } }),
113
116
  });
114
117
 
@@ -124,21 +127,23 @@ describe("createCellAtom", () => {
124
127
  hide_code: true,
125
128
  column: null,
126
129
  });
127
- expect(notebook().cellIds.atOrThrow(1).inOrderIds).toEqual([c2, id]);
130
+ expect(store.get(layoutAtom).columns[1].ids).toEqual([c2, id]);
128
131
  });
129
132
 
130
- it("names the column when appending to an empty column", () => {
131
- const { store, notebook, cellIn } = setup();
132
- store.set(notebookAtom, (state) => ({
133
- ...state,
134
- cellIds: state.cellIds.addColumn(state.cellIds.atOrThrow(0).id),
135
- }));
136
- const empty = notebook().cellIds.atOrThrow(1).id;
133
+ it("fills an emptied column, which keeps its id", () => {
134
+ const { store, columns, columnIds, cellIn } = setup({
135
+ c3: cell("c3", "z = 3", { config: { ...CONFIG, column: 1 } }),
136
+ });
137
+ const [first, second] = columnIds();
138
+ // Moving its only cell out leaves the column on the page, to add to.
139
+ store.set(moveCellAtom, { cellId: c3, direction: "left" });
140
+ expect(columns()).toEqual([[c3, c1, c2], []]);
137
141
 
138
- const id = store.set(createCellAtom, { endOf: empty });
142
+ const id = store.set(createCellAtom, { endOf: second });
139
143
 
140
144
  expect(cellIn(id as string)?.config.column).toBe(1);
141
- expect(notebook().cellIds.atOrThrow(1).inOrderIds).toEqual([id]);
145
+ expect(columns()).toEqual([[c3, c1, c2], [id]]);
146
+ expect(columnIds()).toEqual([first, second]);
142
147
  });
143
148
 
144
149
  it("creates nothing when a cell already has the code", () => {
@@ -161,6 +166,40 @@ describe("createCellAtom", () => {
161
166
  });
162
167
  });
163
168
 
169
+ describe("splitCellAtom", () => {
170
+ it("splits a cell at the cursor and undoes it", () => {
171
+ const { store, order, notebook, cellIn, mountEditor } = setup({
172
+ c1: cell("c1", "line1\nline2"),
173
+ });
174
+ const view = mountEditor(c1);
175
+ view.dispatch({ selection: { anchor: 5 } });
176
+
177
+ const id = store.set(splitCellAtom, { cellId: c1 }) as CellId;
178
+
179
+ expect(order()).toEqual([c1, id, c2]);
180
+ expect(cellIn(c1)?.code).toBe("line1");
181
+ expect(cellIn(id)?.code).toBe("line2");
182
+ expect(view.state.doc.toString()).toBe("line1");
183
+ expect(notebook().scrollKey).toBe(id);
184
+
185
+ store.set(undoSplitCellAtom, {
186
+ cellId: c1,
187
+ newCellId: id,
188
+ snapshot: "line1\nline2",
189
+ });
190
+
191
+ expect(order()).toEqual([c1, c2]);
192
+ expect(cellIn(c1)?.code).toBe("line1\nline2");
193
+ expect(view.state.doc.toString()).toBe("line1\nline2");
194
+ });
195
+
196
+ it("does nothing for a cell without an editor", () => {
197
+ const { store, order } = setup();
198
+ expect(store.set(splitCellAtom, { cellId: c1 })).toBeNull();
199
+ expect(order()).toEqual([c1, c2]);
200
+ });
201
+ });
202
+
164
203
  describe("moveToNextCellAtom", () => {
165
204
  it("focuses the neighbouring cell within the column", () => {
166
205
  const { store, order } = setup();
@@ -218,14 +257,14 @@ describe("addSetupCellAtom", () => {
218
257
  });
219
258
 
220
259
  it("places the setup cell in an empty first column", () => {
221
- const { store, notebook, cellIn } = setup({
260
+ const { store, columns, notebook, cellIn } = setup({
222
261
  c1: cell(c1, "x = 1", { config: { ...CONFIG, column: 1 } }),
223
262
  });
224
- expect(notebook().cellIds.atOrThrow(0).inOrderIds).toEqual([]);
263
+ expect(columns()[0]).toEqual([]);
225
264
 
226
265
  store.set(addSetupCellAtom, { code: "import marimo as mo" });
227
266
 
228
- expect(notebook().cellIds.atOrThrow(0).inOrderIds).toEqual([SETUP_CELL_ID]);
267
+ expect(columns()[0]).toEqual([SETUP_CELL_ID]);
229
268
  expect(cellIn(SETUP_CELL_ID)?.config.column).toBe(0);
230
269
  expect(notebook().scrollKey).toBe(SETUP_CELL_ID);
231
270
  });
@@ -297,16 +336,16 @@ describe("deleteCellsAtom", () => {
297
336
  });
298
337
 
299
338
  it("names the column of a cell that followed a deleted column start", () => {
300
- const { store, notebook, cellIn } = setup({
339
+ const { store, cellIn } = setup({
301
340
  c2: cell("c2", "y = 2", { config: { ...CONFIG, column: 1 } }),
302
341
  c3: cell("c3", "z = 3"),
303
342
  });
304
- expect(notebook().cellIds.atOrThrow(1).inOrderIds).toEqual([c2, c3]);
343
+ expect(store.get(layoutAtom).columns[1].ids).toEqual([c2, c3]);
305
344
 
306
345
  store.set(deleteCellsAtom, { cellIds: [c2] });
307
346
 
308
347
  expect(cellIn(c3)?.config.column).toBe(1);
309
- expect(notebook().cellIds.atOrThrow(1).inOrderIds).toEqual([c3]);
348
+ expect(store.get(layoutAtom).columns[1].ids).toEqual([c3]);
310
349
  });
311
350
 
312
351
  it("forgets a deleted cell's editor-only state and its atoms", () => {
@@ -386,14 +425,14 @@ describe("undoDeleteCellAtom", () => {
386
425
  const { store, order, notebook } = setup({
387
426
  c3: cell("c3", "z = 3"),
388
427
  });
389
- const column = notebook().cellIds.atOrThrow(0);
428
+ const column = store.get(layoutAtom).columns[0];
390
429
  store.set(moveCellsRelativeToAtom, {
391
430
  cellIds: [c1, c2],
392
431
  targetCellId: c3,
393
432
  position: "after",
394
433
  previousPlacements: [
395
- { columnId: column.id, index: 0 as CellIndex },
396
- { columnId: column.id, index: 1 as CellIndex },
434
+ { columnId: column.id, index: 0 },
435
+ { columnId: column.id, index: 1 },
397
436
  ],
398
437
  });
399
438
  expect(order()).toEqual([c3, c1, c2]);