@marimo-team/islands 0.25.3-dev170 → 0.25.3-dev175

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 (196) hide show
  1. package/dist/{ConnectedDataExplorerComponent-BbSXV-Xa.js → ConnectedDataExplorerComponent-Le_OBfmt.js} +9 -9
  2. package/dist/{ErrorBoundary-NE86qEWZ.js → ErrorBoundary-DNvcAIsP.js} +8 -8
  3. package/dist/{abnfDiagram-VCTEODGH-D7n07nXf.js → abnfDiagram-VCTEODGH-Tf1ghyQQ.js} +2 -2
  4. package/dist/{any-language-editor-3E24SnCV.js → any-language-editor-BVFHDDQ3.js} +4 -4
  5. package/dist/{architectureDiagram-5GKGNRK7-D6CjRib5.js → architectureDiagram-5GKGNRK7-xlnbTG0T.js} +1 -1
  6. package/dist/{chat-ui-CC7GekpG.js → chat-ui-X-cWrL9u.js} +1218 -1218
  7. package/dist/{check-SpOMoCLP.js → check-C9Mp9eCK.js} +1 -1
  8. package/dist/{chunk-IMKFNOWR-6dtPz7uS.js → chunk-IMKFNOWR-Cmd4a5jy.js} +1 -1
  9. package/dist/{chunk-SHT3W25Y-BwnFNKDA.js → chunk-SHT3W25Y-mSNp-WpO.js} +1 -1
  10. package/dist/{chunk-SVP7TREG-D2eV4vBJ.js → chunk-SVP7TREG-CWC49D8X.js} +1 -1
  11. package/dist/{chunk-TICWLB2K-BDs4-_-r.js → chunk-TICWLB2K-De9FINtG.js} +1 -1
  12. package/dist/{classDiagram-ZZMXUADV-DCJnf_8v.js → classDiagram-ZZMXUADV-DweTkYdp.js} +2 -2
  13. package/dist/{classDiagram-v2-VYDZK3BY-BOmSzPBD.js → classDiagram-v2-VYDZK3BY-A6MR2TiE.js} +2 -2
  14. package/dist/{code-visibility-Bzvc6ajl.js → code-visibility-CH1lmM1h.js} +5 -5
  15. package/dist/{common-Ciw4zkh2.js → common-DIrS1UR9.js} +1081 -1085
  16. package/dist/{copy-icon-C1trdvVt.js → copy-icon-De584pZ4.js} +2 -2
  17. package/dist/{cynefinDiagram-5FMLGOSQ-Bln_Soky.js → cynefinDiagram-5FMLGOSQ-BSvo5le-.js} +1 -1
  18. package/dist/{dagre-GXQ25YYZ-D-nf7WzW.js → dagre-GXQ25YYZ-tXRQslO6.js} +1 -1
  19. package/dist/{data-grid-overlay-editor-AQREmxI7.js → data-grid-overlay-editor-DFLZvFK0.js} +1 -1
  20. package/dist/{diagram-S7CK7UJ4-PsgSMkY2.js → diagram-S7CK7UJ4-r3t5VtwI.js} +1 -1
  21. package/dist/{diagram-UQ7AKVKN-DfwfUuSw.js → diagram-UQ7AKVKN-qgbC8YnT.js} +1 -1
  22. package/dist/{diagram-VX7I27RA-C9_e-oMc.js → diagram-VX7I27RA-CiHxEwtp.js} +1 -1
  23. package/dist/{diagram-Z3DM3KII-CWLa5tuY.js → diagram-Z3DM3KII-D95u15Ev.js} +1 -1
  24. package/dist/{dialog-DgeqyrEZ.js → dialog-DVRfQuhD.js} +24 -24
  25. package/dist/{dist-F8Ep36rk.js → dist-B1M0bGO3.js} +1 -1
  26. package/dist/{dist-D6d86eNJ.js → dist-BA13RJj5.js} +1 -1
  27. package/dist/{dist-Bvs24R_E.js → dist-BJtmSgcE.js} +48 -48
  28. package/dist/{dist-CN_BeG2I.js → dist-Ba2VIn1K.js} +53 -53
  29. package/dist/{dist-PXYI_yuP.js → dist-Bj4bmwUm.js} +12 -12
  30. package/dist/{dist-DoE4XJBC.js → dist-BmS-ZGjl.js} +8626 -8626
  31. package/dist/{dist-BorMD-uX.js → dist-Bzogh7CW.js} +87 -87
  32. package/dist/{dist-CN8zI-XC.js → dist-CThPVKaP.js} +1385 -1382
  33. package/dist/{dist-DiDibOYr.js → dist-CTi5FTiV.js} +87 -87
  34. package/dist/{dist-diIx5qJW.js → dist-Ck_C32ye.js} +2 -2
  35. package/dist/{dist-BLfKmR-l.js → dist-D1fnFp1J.js} +1 -1
  36. package/dist/{dist-OR1OnN-y.js → dist-D3x9__Fu.js} +46 -46
  37. package/dist/{dist-BMQf5zIS.js → dist-D7mrh5sO.js} +36 -36
  38. package/dist/{dist-CSJ0MDRw.js → dist-D9Q3xjiP.js} +1 -1
  39. package/dist/{dist-BuaRHxOc.js → dist-DP60Ubh8.js} +2 -2
  40. package/dist/{dist-CSbh1PNP.js → dist-DTv9KkpW.js} +12 -12
  41. package/dist/{dist-D-wwqlzJ.js → dist-DVVwOfrD.js} +109 -109
  42. package/dist/{dist-C8fAKSH1.js → dist-DYXM73-k.js} +36 -36
  43. package/dist/{dist-DBtSl3sf.js → dist-DjeybPOb.js} +1 -1
  44. package/dist/{dist-CrSP_H1M.js → dist-DtGn9N4h.js} +197 -197
  45. package/dist/{dist-CLdpGQVr.js → dist-KN80qWCP.js} +2 -2
  46. package/dist/{dist-Cg7gzw-a.js → dist-O0qFpEJh.js} +56 -56
  47. package/dist/{dist-CkTTIdBe.js → dist-OUle2RT7.js} +207 -207
  48. package/dist/{dist-BK4Oj99o.js → dist-T5rh6JvL.js} +1 -1
  49. package/dist/{dropdown-menu-B6ftyzOg.js → dropdown-menu-DCMbbQ35.js} +691 -691
  50. package/dist/{ebnfDiagram-PWID7BFC-B1QRM58v.js → ebnfDiagram-PWID7BFC-P7e4bryK.js} +2 -2
  51. package/dist/{erDiagram-RLTQ6QDP-DJK_FqvW.js → erDiagram-RLTQ6QDP-CUHZcmRj.js} +1 -1
  52. package/dist/{error-banner-D40pmfQQ.js → error-banner-DxJ3s8YO.js} +57 -57
  53. package/dist/{error-entries-DgK3wUJR.js → error-entries-mpoD5t79.js} +4985 -5006
  54. package/dist/{esm-D2gTsXEV.js → esm-Bbw_Njjx.js} +575 -575
  55. package/dist/{esm-AMFssoNp.js → esm-mDQNEFe_.js} +354 -354
  56. package/dist/{feedback-button-8a6SETmT.js → feedback-button-DL0DJ-LB.js} +30 -30
  57. package/dist/{flowDiagram-HODETNUW-Uxc25XSJ.js → flowDiagram-HODETNUW-BGZ95AzI.js} +2 -2
  58. package/dist/{formats-DQCyPd5Z.js → formats-DI7HEVv7.js} +1 -1
  59. package/dist/{glide-data-editor-B70oke_v.js → glide-data-editor-n4wnx19S.js} +10 -10
  60. package/dist/{glide-utils-Dz_F4pAY.js → glide-utils-D98laidb.js} +2 -2
  61. package/dist/{globals-Cxd5hNlI.js → globals-NCsYyjpB.js} +3 -3
  62. package/dist/{html-to-image-D4yG5Ru0.js → html-to-image-BoZthB4P.js} +467 -458
  63. package/dist/{infoDiagram-27XIBGKW-DmjxqwL0.js → infoDiagram-27XIBGKW-BF0YX70D.js} +1 -1
  64. package/dist/{ishikawaDiagram-5VMMS53U-NgCLHqQi.js → ishikawaDiagram-5VMMS53U-_itsAAn-.js} +1 -1
  65. package/dist/{kanban-definition-UXKFOSKX-CZaG-iam.js → kanban-definition-UXKFOSKX-VNW2Beqh.js} +1 -1
  66. package/dist/{label-D9_-zSg-.js → label-Qvw1E97W.js} +1 -1
  67. package/dist/main.js +8257 -6606
  68. package/dist/{mermaid-CSvw7c71.js → mermaid-CZ6FrClS.js} +35 -35
  69. package/dist/{mindmap-definition-YA3MSWOX-DJYQBDH_.js → mindmap-definition-YA3MSWOX-DR4ripvd.js} +1 -1
  70. package/dist/{number-overlay-editor-ChK-SKUk.js → number-overlay-editor-rlvo4mo3.js} +1 -1
  71. package/dist/{pegDiagram-XKGWAZYB-CHKO_dAe.js → pegDiagram-XKGWAZYB-CFr3XlGl.js} +2 -2
  72. package/dist/{pieDiagram-E7YTZNPT-DVevbMAo.js → pieDiagram-E7YTZNPT-j91XYSLY.js} +1 -1
  73. package/dist/{process-output-DxvBAeL3.js → process-output-DLS4Mqg_.js} +4 -4
  74. package/dist/{railroadDiagram-O6MQD6OU-Detj1kcB.js → railroadDiagram-O6MQD6OU-Cd47ZV9E.js} +2 -2
  75. package/dist/{requirementDiagram-BXWQKSXE-BLFD0T_Z.js → requirementDiagram-BXWQKSXE-eJH6gLtX.js} +1 -1
  76. package/dist/{reveal-component-CXMXeDZD.js → reveal-component-BJfmRUzb.js} +9 -9
  77. package/dist/{select-CkF_GBxO.js → select-2KOiReNY.js} +324 -324
  78. package/dist/{sequenceDiagram-WJ2MYXX4-BZBgatQV.js → sequenceDiagram-WJ2MYXX4-DdfFEIrm.js} +1 -1
  79. package/dist/{spec-Mnkn3XEr.js → spec-h69nC3SI.js} +4 -4
  80. package/dist/{stateDiagram-D77RDMKH-4BZW9-6_.js → stateDiagram-D77RDMKH-DkjDvip8.js} +2 -2
  81. package/dist/{stateDiagram-v2-MP3YSRHH-C4sKC8Iq.js → stateDiagram-v2-MP3YSRHH-DBHACnOo.js} +2 -2
  82. package/dist/{swimlanes-42K2YHIH-SL5yeGe2.js → swimlanes-42K2YHIH-ogImlOmp.js} +1 -1
  83. package/dist/{swimlanesDiagram-VR7AAH4N-DqPmAYUv.js → swimlanesDiagram-VR7AAH4N-D6x-zsci.js} +2 -2
  84. package/dist/{timeline-definition-24CTP7MA-BfXUMWkq.js → timeline-definition-24CTP7MA-DQpLtsHD.js} +1 -1
  85. package/dist/{toDate-TMubKIVL.js → toDate-CDzoofJo.js} +2 -2
  86. package/dist/{toast-Btbve3Lm.js → toast-B5t9o6s_.js} +47 -47
  87. package/dist/{tooltip-CJ4Rqasp.js → tooltip-s4zZxwKe.js} +67 -67
  88. package/dist/{use-restore-focus-CmGwRNId.js → use-restore-focus-BgDTd2tc.js} +1 -1
  89. package/dist/{useAsyncData-DjMGFzFy.js → useAsyncData-Bn8SxUqM.js} +1 -1
  90. package/dist/{useDateFormatter-eCL_SdeG.js → useDateFormatter-D3Tkfzej.js} +2 -2
  91. package/dist/{useDeepCompareMemoize-BQR5TJot.js → useDeepCompareMemoize-CC2tnPCk.js} +1 -1
  92. package/dist/{useLifecycle-C_GvKWbQ.js → useLifecycle-BJlxHqWa.js} +2 -2
  93. package/dist/{useTheme-Bns2gJZ6.js → useTheme-DMfQYmlG.js} +85 -85
  94. package/dist/{vega-component-BsVptuhz.js → vega-component-CovWHi60.js} +7 -7
  95. package/dist/{vennDiagram-4TSXK5OY-CH65znGa.js → vennDiagram-4TSXK5OY-BD-SL_vq.js} +1 -1
  96. package/dist/{wardleyDiagram-VM6X3IG4-z9pWKb2_.js → wardleyDiagram-VM6X3IG4-BDgJV4ah.js} +1 -1
  97. package/dist/{xychartDiagram-S5SC5T6Z-MxyaWjjq.js → xychartDiagram-S5SC5T6Z-1hIHN6Rl.js} +1 -1
  98. package/package.json +1 -1
  99. package/src/__mocks__/document.tsx +54 -0
  100. package/src/__mocks__/notebook.ts +2 -1
  101. package/src/__mocks__/requests.ts +0 -2
  102. package/src/components/chat/chat-panel.tsx +3 -3
  103. package/src/components/datasources/__tests__/column-preview.test.tsx +6 -8
  104. package/src/components/datasources/column-preview.tsx +4 -8
  105. package/src/components/datasources/datasources.tsx +4 -3
  106. package/src/components/dependency-graph/minimap-content.tsx +2 -6
  107. package/src/components/dependency-graph/panels.tsx +2 -1
  108. package/src/components/editor/__tests__/Output.test.tsx +50 -32
  109. package/src/components/editor/__tests__/data-attributes.test.tsx +22 -11
  110. package/src/components/editor/__tests__/output-persistence.test.tsx +6 -1
  111. package/src/components/editor/actions/__tests__/name-cell-input.test.tsx +69 -0
  112. package/src/components/editor/actions/name-cell-input.tsx +4 -3
  113. package/src/components/editor/actions/useCellActionButton.tsx +20 -26
  114. package/src/components/editor/actions/useNotebookActions.tsx +13 -18
  115. package/src/components/editor/actions/useSetCodeVisibility.ts +10 -11
  116. package/src/components/editor/actions/useSetOutputsExpanded.ts +13 -18
  117. package/src/components/editor/cell/CreateCellButton.tsx +5 -9
  118. package/src/components/editor/cell/__tests__/StagedAICell.test.tsx +3 -1
  119. package/src/components/editor/cell/code/cell-editor.tsx +13 -20
  120. package/src/components/editor/cell/useAddCell.ts +4 -8
  121. package/src/components/editor/cell/useDeleteCell.tsx +10 -36
  122. package/src/components/editor/cell/useRunCells.ts +2 -5
  123. package/src/components/editor/cell/useSplitCell.tsx +2 -1
  124. package/src/components/editor/chrome/panels/logs-panel.tsx +2 -1
  125. package/src/components/editor/chrome/panels/snippets-panel.tsx +5 -11
  126. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +3 -1
  127. package/src/components/editor/columns/sortable-column.tsx +1 -1
  128. package/src/components/editor/connections/components.tsx +4 -9
  129. package/src/components/editor/controls/Controls.tsx +1 -1
  130. package/src/components/editor/controls/__tests__/notebook-menu-dropdown.test.tsx +9 -3
  131. package/src/components/editor/documentation.css +34 -11
  132. package/src/components/editor/errors/auto-fix.tsx +4 -8
  133. package/src/components/editor/file-tree/file-explorer.tsx +3 -7
  134. package/src/components/editor/links/__tests__/cell-link.test.tsx +3 -1
  135. package/src/components/editor/links/cell-link.tsx +2 -1
  136. package/src/components/editor/navigation/__tests__/clipboard.test.ts +60 -38
  137. package/src/components/editor/navigation/__tests__/navigation.test.ts +73 -64
  138. package/src/components/editor/navigation/clipboard.ts +12 -11
  139. package/src/components/editor/navigation/multi-cell-action-toolbar.tsx +11 -20
  140. package/src/components/editor/navigation/navigation.ts +20 -28
  141. package/src/components/editor/notebook-cell.tsx +7 -10
  142. package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +125 -69
  143. package/src/components/editor/renderers/cell-array.tsx +10 -17
  144. package/src/components/markdown/markdown-renderer.tsx +4 -8
  145. package/src/components/pages/edit-page.tsx +32 -17
  146. package/src/components/scratchpad/scratchpad.test.tsx +3 -1
  147. package/src/components/scratchpad/scratchpad.tsx +4 -7
  148. package/src/components/slides/__tests__/minimap-actions.test.tsx +9 -25
  149. package/src/components/slides/minimap.tsx +7 -3
  150. package/src/components/slides/slide-cell-view.tsx +3 -3
  151. package/src/components/sort/SortableCellsProvider.tsx +2 -1
  152. package/src/core/ai/__tests__/staged-cells.test.ts +18 -20
  153. package/src/core/ai/staged-cells.ts +8 -16
  154. package/src/core/ai/tools/__tests__/edit-notebook-tool.test.ts +2 -2
  155. package/src/core/ai/tools/__tests__/run-cells-tool.test.ts +2 -2
  156. package/src/core/ai/tools/base.ts +2 -2
  157. package/src/core/ai/tools/edit-notebook-tool.ts +13 -21
  158. package/src/core/cells/__tests__/actions.test.ts +474 -34
  159. package/src/core/cells/__tests__/add-missing-import.test.ts +7 -8
  160. package/src/core/cells/__tests__/cells.test.ts +251 -861
  161. package/src/core/cells/__tests__/document.test.ts +42 -38
  162. package/src/core/cells/__tests__/notebook-outline.test.ts +7 -1
  163. package/src/core/cells/__tests__/outputs.test.tsx +71 -53
  164. package/src/core/cells/actions.ts +391 -3
  165. package/src/core/cells/add-missing-import.ts +46 -42
  166. package/src/core/cells/cells.ts +73 -444
  167. package/src/core/cells/document.ts +38 -6
  168. package/src/core/cells/outputs.ts +12 -28
  169. package/src/core/codemirror/cells/extensions.ts +6 -6
  170. package/src/core/codemirror/cells/state.ts +1 -1
  171. package/src/core/codemirror/keymaps/vim.ts +2 -2
  172. package/src/core/edit-app.tsx +20 -25
  173. package/src/core/islands/__tests__/bootstrap.test.ts +56 -0
  174. package/src/core/islands/__tests__/bridge.test.ts +42 -0
  175. package/src/core/islands/bootstrap.ts +24 -1
  176. package/src/core/islands/bridge.ts +46 -5
  177. package/src/core/kernel/__tests__/handlers.test.ts +7 -153
  178. package/src/core/kernel/handlers.ts +18 -59
  179. package/src/core/network/__tests__/requests-lazy.test.ts +1 -3
  180. package/src/core/network/requests-lazy.ts +0 -2
  181. package/src/core/network/requests-network.ts +0 -16
  182. package/src/core/network/requests-static.ts +0 -2
  183. package/src/core/network/requests-toasting.tsx +0 -2
  184. package/src/core/network/types.ts +0 -7
  185. package/src/core/notebook/document/__tests__/codemirror.test.ts +5 -0
  186. package/src/core/notebook/document/__tests__/draft.test.ts +41 -0
  187. package/src/core/notebook/document/__tests__/state.test.ts +21 -0
  188. package/src/core/notebook/document/draft.ts +35 -5
  189. package/src/core/notebook/document/index.ts +1 -0
  190. package/src/core/notebook/document/state.ts +6 -2
  191. package/src/core/run-app.tsx +1 -7
  192. package/src/core/session/__tests__/useNotebookSession.hook.test.tsx +0 -6
  193. package/src/core/session/useNotebookSession.tsx +28 -35
  194. package/src/core/wasm/bridge.ts +0 -16
  195. package/src/css/globals.css +56 -22
  196. package/src/stories/runtime-story.tsx +22 -15
@@ -4,9 +4,11 @@ import { python } from "@codemirror/lang-python";
4
4
  import { EditorState } from "@codemirror/state";
5
5
  import { EditorView } from "@codemirror/view";
6
6
  import { createStore } from "jotai";
7
+ import { createRef } from "react";
7
8
  import {
8
9
  afterAll,
9
10
  beforeAll,
11
+ afterEach,
10
12
  beforeEach,
11
13
  describe,
12
14
  expect,
@@ -16,11 +18,12 @@ import {
16
18
  import { MockNotebook } from "@/__mocks__/notebook";
17
19
  import { cellId } from "@/__tests__/branded";
18
20
  import type { CellHandle } from "@/components/editor/notebook-cell";
19
- import { CellId, SCRATCH_CELL_ID, SETUP_CELL_ID } from "@/core/cells/ids";
21
+ import { CellId, SCRATCH_CELL_ID } from "@/core/cells/ids";
20
22
  import { foldAllBulk, unfoldAllBulk } from "@/core/codemirror/editing/commands";
21
23
  import { adaptiveLanguageConfiguration } from "@/core/codemirror/language/extension";
22
24
  import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
23
25
  import type { OutputMessage } from "@/core/kernel/messages";
26
+ import type { CellConfig } from "@/core/network/types";
24
27
  import {
25
28
  type CollapsibleTree,
26
29
  MultiColumn,
@@ -38,7 +41,12 @@ import {
38
41
  scrollToBottom,
39
42
  scrollToTop,
40
43
  } from "../scrollCellIntoView";
41
- import type { CellData } from "../types";
44
+ import {
45
+ type CellData,
46
+ createCell,
47
+ createCellConfig,
48
+ createCellRuntimeState,
49
+ } from "../types";
42
50
 
43
51
  vi.mock("@/core/codemirror/editing/commands", () => ({
44
52
  foldAllBulk: vi.fn(),
@@ -119,8 +127,7 @@ describe("cell reducer", () => {
119
127
  let cells: ReturnType<typeof flattenTopLevelNotebookCells>;
120
128
  let firstCellId: CellId;
121
129
 
122
- const actions = createActions((action) => {
123
- state = reducer(state, action);
130
+ const mountEditors = () => {
124
131
  for (const [cellIdString, handle] of Object.entries(state.cellHandles)) {
125
132
  // @ts-expect-error - Typescript Object.entries doesn't know that keys are CellId
126
133
  const cid: CellId = cellIdString;
@@ -134,7 +141,76 @@ describe("cell reducer", () => {
134
141
  }
135
142
  }
136
143
  cells = flattenTopLevelNotebookCells(state);
137
- });
144
+ };
145
+ const actions = createActions((action) => {
146
+ state = reducer(state, action);
147
+ mountEditors();
148
+ });
149
+
150
+ /** Put a cell into the notebook the way the document would. */
151
+ function seedCell({
152
+ cellId,
153
+ before,
154
+ code = "",
155
+ name,
156
+ config,
157
+ newCellId,
158
+ hideCode,
159
+ autoFocus = true,
160
+ }: {
161
+ cellId: CellId | "__end__" | { type: "__end__"; columnId: CellColumnId };
162
+ before: boolean;
163
+ code?: string;
164
+ name?: string;
165
+ config?: CellConfig;
166
+ newCellId?: CellId;
167
+ hideCode?: boolean;
168
+ autoFocus?: boolean;
169
+ }): CellId {
170
+ let column: CollapsibleTree<CellId>;
171
+ let cellIndex: number;
172
+ if (cellId === "__end__") {
173
+ column = state.cellIds.atOrThrow(0);
174
+ cellIndex = column.length;
175
+ } else if (typeof cellId === "string") {
176
+ column = state.cellIds.findWithId(cellId);
177
+ cellIndex = column.topLevelIds.indexOf(cellId);
178
+ } else {
179
+ column = state.cellIds.get(cellId.columnId) ?? state.cellIds.atOrThrow(0);
180
+ cellIndex = column.length;
181
+ }
182
+ const id = newCellId ?? CellId.create();
183
+ state = {
184
+ ...state,
185
+ cellIds: state.cellIds.insertId(
186
+ id,
187
+ column.id,
188
+ before ? cellIndex : cellIndex + 1,
189
+ ),
190
+ cellData: {
191
+ ...state.cellData,
192
+ [id]: createCell({
193
+ id,
194
+ code,
195
+ name,
196
+ config: createCellConfig({
197
+ ...config,
198
+ ...(hideCode != null && { hide_code: hideCode }),
199
+ }),
200
+ edited: Boolean(code),
201
+ }),
202
+ },
203
+ cellRuntime: { ...state.cellRuntime, [id]: createCellRuntimeState() },
204
+ cellHandles: { ...state.cellHandles, [id]: createRef() },
205
+ scrollKey: autoFocus ? id : null,
206
+ untouchedNewCells:
207
+ hideCode && autoFocus
208
+ ? new Set([...state.untouchedNewCells, id])
209
+ : state.untouchedNewCells,
210
+ };
211
+ mountEditors();
212
+ return id;
213
+ }
138
214
 
139
215
  let i = 0;
140
216
  const originalCreate = CellId.create.bind(CellId);
@@ -150,7 +226,7 @@ describe("cell reducer", () => {
150
226
 
151
227
  state = initialNotebookState();
152
228
  state.cellIds = MultiColumn.from([]);
153
- actions.createNewCell({ cellId: "__end__", before: false });
229
+ seedCell({ cellId: "__end__", before: false });
154
230
  firstCellId = state.cellIds.inOrderIds[0];
155
231
  });
156
232
 
@@ -158,181 +234,6 @@ describe("cell reducer", () => {
158
234
  CellId.create = originalCreate;
159
235
  });
160
236
 
161
- it("can add a cell after another cell", () => {
162
- actions.createNewCell({
163
- cellId: firstCellId,
164
- before: false,
165
- });
166
- expect(formatCells(state)).toMatchInlineSnapshot(`
167
- "
168
- [0] ''
169
-
170
- [1] ''
171
- "
172
- `);
173
- });
174
-
175
- it("can add a cell to the end with code", () => {
176
- actions.createNewCell({
177
- cellId: "__end__",
178
- code: "import numpy as np",
179
- before: false,
180
- });
181
- expect(formatCells(state)).toMatchInlineSnapshot(`
182
- "
183
- [0] ''
184
-
185
- [1] 'import numpy as np'
186
- "
187
- `);
188
-
189
- // Cell should be added to the end and edited
190
- expect(cells[1].code).toBe("import numpy as np");
191
- expect(cells[1].edited).toBe(true);
192
- expect(cells[1].lastCodeRun).toBe(null);
193
- });
194
-
195
- it("can add a cell before another cell", () => {
196
- actions.createNewCell({
197
- cellId: firstCellId,
198
- before: true,
199
- });
200
- expect(formatCells(state)).toMatchInlineSnapshot(`
201
- "
202
- [1] ''
203
-
204
- [0] ''
205
- "
206
- `);
207
- });
208
-
209
- it("can add a cell with name and config", () => {
210
- actions.createNewCell({
211
- cellId: firstCellId,
212
- before: false,
213
- code: "x = 1",
214
- name: "My Cell",
215
- config: { hide_code: true, disabled: false },
216
- });
217
- const newCellId = state.cellIds.inOrderIds[1];
218
- expect(state.cellData[newCellId].name).toBe("My Cell");
219
- expect(state.cellData[newCellId].config.hide_code).toBe(true);
220
- expect(state.cellData[newCellId].config.disabled).toBe(false);
221
- });
222
-
223
- it("can delete a Python cell and undo delete", () => {
224
- actions.createNewCell({
225
- cellId: firstCellId,
226
- before: false,
227
- });
228
- actions.deleteCell({
229
- cellId: firstCellId,
230
- });
231
- expect(formatCells(state)).toMatchInlineSnapshot(`
232
- "
233
- [1] ''
234
- "
235
- `);
236
-
237
- // undo
238
- actions.undoDeleteCell();
239
- expect(formatCells(state)).toMatchInlineSnapshot(`
240
- "
241
- [2] ''
242
-
243
- [1] ''
244
- "
245
- `);
246
-
247
- // Verify scrollKey is set to the restored cell
248
- expect(state.scrollKey).toBe("2");
249
- });
250
-
251
- it("can delete a SQL cell and undo delete", () => {
252
- actions.createNewCell({
253
- cellId: firstCellId,
254
- before: false,
255
- code: `df = mo.sql("""SELECT * FROM table""")`,
256
- });
257
- const newCellId = state.cellIds.getColumns()[0].atOrThrow(1);
258
- actions.deleteCell({
259
- cellId: newCellId,
260
- });
261
- expect(formatCells(state)).toMatchInlineSnapshot(`
262
- "
263
- [0] ''
264
- "
265
- `);
266
-
267
- // undo
268
- actions.undoDeleteCell();
269
- expect(formatCells(state)).toMatchInlineSnapshot(`
270
- "
271
- [0] ''
272
-
273
- [2] 'df = mo.sql("""SELECT * FROM table""")'
274
- "
275
- `);
276
- });
277
-
278
- it("can delete a Markdown cell and undo delete", () => {
279
- const text = "The quick brown fox jumps over the lazy dog.";
280
- actions.createNewCell({
281
- cellId: firstCellId,
282
- before: false,
283
- code: `mo.md(r"""${text}""")`,
284
- });
285
- const newCellId = state.cellIds.getColumns()[0].atOrThrow(1);
286
- actions.deleteCell({
287
- cellId: newCellId,
288
- });
289
- expect(formatCells(state)).toMatchInlineSnapshot(`
290
- "
291
- [0] ''
292
- "
293
- `);
294
-
295
- // undo
296
- actions.undoDeleteCell();
297
- expect(formatCells(state)).toMatchInlineSnapshot(`
298
- "
299
- [0] ''
300
-
301
- [2] 'mo.md(r"""The quick brown fox jumps over the lazy dog.""")'
302
- "
303
- `);
304
- });
305
-
306
- it("undo delete restores cell config (hide_code, disabled)", () => {
307
- actions.createNewCell({
308
- cellId: firstCellId,
309
- before: false,
310
- code: "x = 1",
311
- });
312
- const newCellId = state.cellIds.getColumns()[0].atOrThrow(1);
313
-
314
- // Set config to hidden and disabled
315
- actions.updateCellConfig({
316
- cellId: newCellId,
317
- config: { hide_code: true, disabled: true },
318
- });
319
- expect(state.cellData[newCellId].config.hide_code).toBe(true);
320
- expect(state.cellData[newCellId].config.disabled).toBe(true);
321
-
322
- // Delete the cell
323
- actions.deleteCell({ cellId: newCellId });
324
- expect(state.cellIds.inOrderIds).not.toContain(newCellId);
325
-
326
- // Undo delete
327
- actions.undoDeleteCell();
328
- const restoredCellId =
329
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
330
-
331
- // Config should be preserved
332
- expect(state.cellData[restoredCellId].config.hide_code).toBe(true);
333
- expect(state.cellData[restoredCellId].config.disabled).toBe(true);
334
- });
335
-
336
237
  it("can update a cell", () => {
337
238
  actions.updateCellCode({
338
239
  cellId: firstCellId,
@@ -347,7 +248,7 @@ describe("cell reducer", () => {
347
248
  });
348
249
 
349
250
  it("can move a cell", () => {
350
- actions.createNewCell({
251
+ seedCell({
351
252
  cellId: firstCellId,
352
253
  before: false,
353
254
  });
@@ -461,11 +362,11 @@ describe("cell reducer", () => {
461
362
  });
462
363
 
463
364
  it("can drag and drop a cell", () => {
464
- actions.createNewCell({
365
+ seedCell({
465
366
  cellId: firstCellId,
466
367
  before: false,
467
368
  });
468
- actions.createNewCell({
369
+ seedCell({
469
370
  cellId: cellId("1"),
470
371
  before: false,
471
372
  });
@@ -511,11 +412,11 @@ describe("cell reducer", () => {
511
412
  });
512
413
 
513
414
  it("can move a cell to an exact index within a column", () => {
514
- actions.createNewCell({
415
+ seedCell({
515
416
  cellId: firstCellId,
516
417
  before: false,
517
418
  });
518
- actions.createNewCell({
419
+ seedCell({
519
420
  cellId: cellId("1"),
520
421
  before: false,
521
422
  });
@@ -539,11 +440,11 @@ describe("cell reducer", () => {
539
440
  });
540
441
 
541
442
  it("can move a cell to an exact index across columns", () => {
542
- actions.createNewCell({
443
+ seedCell({
543
444
  cellId: firstCellId,
544
445
  before: false,
545
446
  });
546
- actions.createNewCell({
447
+ seedCell({
547
448
  cellId: cellId("1"),
548
449
  before: false,
549
450
  });
@@ -572,11 +473,11 @@ describe("cell reducer", () => {
572
473
  });
573
474
 
574
475
  it("moveCellToIndex is a no-op when moving to the same position", () => {
575
- actions.createNewCell({
476
+ seedCell({
576
477
  cellId: firstCellId,
577
478
  before: false,
578
479
  });
579
- actions.createNewCell({
480
+ seedCell({
580
481
  cellId: cellId("1"),
581
482
  before: false,
582
483
  });
@@ -594,7 +495,7 @@ describe("cell reducer", () => {
594
495
  });
595
496
 
596
497
  it("moveCellToIndex is a no-op for an invalid columnId", () => {
597
- actions.createNewCell({
498
+ seedCell({
598
499
  cellId: firstCellId,
599
500
  before: false,
600
501
  });
@@ -611,11 +512,11 @@ describe("cell reducer", () => {
611
512
  });
612
513
 
613
514
  it("can move multiple cells relative to target", () => {
614
- actions.createNewCell({
515
+ seedCell({
615
516
  cellId: firstCellId,
616
517
  before: false,
617
518
  });
618
- actions.createNewCell({
519
+ seedCell({
619
520
  cellId: cellId("1"),
620
521
  before: false,
621
522
  });
@@ -646,143 +547,6 @@ describe("cell reducer", () => {
646
547
  `);
647
548
  });
648
549
 
649
- it("can undo cut-paste (move with previousPlacements)", () => {
650
- actions.createNewCell({
651
- cellId: firstCellId,
652
- before: false,
653
- });
654
- actions.createNewCell({
655
- cellId: cellId("1"),
656
- before: false,
657
- });
658
- expect(formatCells(state)).toMatchInlineSnapshot(`
659
- "
660
- [0] ''
661
-
662
- [1] ''
663
-
664
- [2] ''
665
- "
666
- `);
667
-
668
- const col = state.cellIds.findWithId(firstCellId);
669
- const previousPlacements = [
670
- {
671
- columnId: col.id,
672
- index: col.indexOfOrThrow(
673
- firstCellId,
674
- ) as import("@/utils/id-tree").CellIndex,
675
- },
676
- {
677
- columnId: col.id,
678
- index: col.indexOfOrThrow(
679
- cellId("1"),
680
- ) as import("@/utils/id-tree").CellIndex,
681
- },
682
- ];
683
-
684
- actions.moveCellsRelativeTo({
685
- cellIds: [firstCellId, cellId("1")],
686
- targetCellId: cellId("2"),
687
- position: "after",
688
- previousPlacements,
689
- });
690
- expect(formatCells(state)).toMatchInlineSnapshot(`
691
- "
692
- [2] ''
693
-
694
- [0] ''
695
-
696
- [1] ''
697
- "
698
- `);
699
-
700
- actions.undoDeleteCell();
701
- expect(formatCells(state)).toMatchInlineSnapshot(`
702
- "
703
- [0] ''
704
-
705
- [1] ''
706
-
707
- [2] ''
708
- "
709
- `);
710
- });
711
-
712
- it("undo order: cut-paste then delete — first undo restores delete, second undo undoes move", () => {
713
- actions.createNewCell({
714
- cellId: firstCellId,
715
- before: false,
716
- });
717
- actions.createNewCell({
718
- cellId: cellId("1"),
719
- before: false,
720
- });
721
-
722
- const col = state.cellIds.findWithId(firstCellId);
723
- const previousPlacements = [
724
- {
725
- columnId: col.id,
726
- index: col.indexOfOrThrow(
727
- firstCellId,
728
- ) as import("@/utils/id-tree").CellIndex,
729
- },
730
- {
731
- columnId: col.id,
732
- index: col.indexOfOrThrow(
733
- cellId("1"),
734
- ) as import("@/utils/id-tree").CellIndex,
735
- },
736
- ];
737
-
738
- actions.moveCellsRelativeTo({
739
- cellIds: [firstCellId, cellId("1")],
740
- targetCellId: cellId("2"),
741
- position: "after",
742
- previousPlacements,
743
- });
744
- expect(formatCells(state)).toMatchInlineSnapshot(`
745
- "
746
- [2] ''
747
-
748
- [0] ''
749
-
750
- [1] ''
751
- "
752
- `);
753
-
754
- actions.deleteCell({ cellId: cellId("2") });
755
- expect(formatCells(state)).toMatchInlineSnapshot(`
756
- "
757
- [0] ''
758
-
759
- [1] ''
760
- "
761
- `);
762
-
763
- actions.undoDeleteCell();
764
- expect(formatCells(state)).toMatchInlineSnapshot(`
765
- "
766
- [3] ''
767
-
768
- [0] ''
769
-
770
- [1] ''
771
- "
772
- `);
773
-
774
- actions.undoDeleteCell();
775
- expect(formatCells(state)).toMatchInlineSnapshot(`
776
- "
777
- [0] ''
778
-
779
- [1] ''
780
-
781
- [3] ''
782
- "
783
- `);
784
- });
785
-
786
550
  it("keeps error evidence after an edit and a new queued run", () => {
787
551
  const code = "raise ValueError('old failure')";
788
552
  const output: OutputMessage = {
@@ -1166,7 +930,7 @@ describe("cell reducer", () => {
1166
930
  formattingChange: false,
1167
931
  });
1168
932
  // Add cell
1169
- actions.createNewCell({
933
+ seedCell({
1170
934
  cellId: firstCellId,
1171
935
  before: false,
1172
936
  });
@@ -1271,33 +1035,6 @@ describe("cell reducer", () => {
1271
1035
  expect(cell.edited).toBe(false);
1272
1036
  });
1273
1037
 
1274
- it("can update a cells config", () => {
1275
- const firstCellId = cells[0].id;
1276
- let cell = cells[0];
1277
- // Starts empty
1278
- expect(cell.config).toEqual({
1279
- disabled: false,
1280
- hide_code: false,
1281
- column: null,
1282
- expand_output: false,
1283
- });
1284
-
1285
- actions.updateCellConfig({
1286
- cellId: firstCellId,
1287
- config: { disabled: true },
1288
- });
1289
- cell = cells[0];
1290
- expect(cell.config.disabled).toBe(true);
1291
-
1292
- // Revert
1293
- actions.updateCellConfig({
1294
- cellId: firstCellId,
1295
- config: { disabled: false },
1296
- });
1297
- cell = cells[0];
1298
- expect(cell.config.disabled).toBe(false);
1299
- });
1300
-
1301
1038
  it("can run a stopped cell", () => {
1302
1039
  // Update code of first cell
1303
1040
  actions.updateCellCode({
@@ -1703,8 +1440,8 @@ describe("cell reducer", () => {
1703
1440
  });
1704
1441
 
1705
1442
  it("can send a cell to the top", () => {
1706
- actions.createNewCell({ cellId: firstCellId, before: false });
1707
- actions.createNewCell({ cellId: cellId("1"), before: false });
1443
+ seedCell({ cellId: firstCellId, before: false });
1444
+ seedCell({ cellId: cellId("1"), before: false });
1708
1445
  actions.sendToTop({ cellId: cellId("2") });
1709
1446
  expect(formatCells(state)).toMatchInlineSnapshot(`
1710
1447
  "
@@ -1718,8 +1455,8 @@ describe("cell reducer", () => {
1718
1455
  });
1719
1456
 
1720
1457
  it("can send a cell to the bottom", () => {
1721
- actions.createNewCell({ cellId: firstCellId, before: false });
1722
- actions.createNewCell({ cellId: cellId("1"), before: false });
1458
+ seedCell({ cellId: firstCellId, before: false });
1459
+ seedCell({ cellId: cellId("1"), before: false });
1723
1460
  actions.sendToBottom({ cellId: firstCellId });
1724
1461
  expect(formatCells(state)).toMatchInlineSnapshot(`
1725
1462
  "
@@ -1733,8 +1470,8 @@ describe("cell reducer", () => {
1733
1470
  });
1734
1471
 
1735
1472
  it("can focus cells", () => {
1736
- actions.createNewCell({ cellId: firstCellId, before: false });
1737
- actions.createNewCell({ cellId: cellId("1"), before: false });
1473
+ seedCell({ cellId: firstCellId, before: false });
1474
+ seedCell({ cellId: cellId("1"), before: false });
1738
1475
 
1739
1476
  actions.focusCell({ cellId: cellId("1"), where: "before" });
1740
1477
  expect(focusAndScrollCellIntoView).toHaveBeenCalledWith(
@@ -1750,11 +1487,6 @@ describe("cell reducer", () => {
1750
1487
  expect(scrollToBottom).toHaveBeenCalled();
1751
1488
  });
1752
1489
 
1753
- it("can update cell name", () => {
1754
- actions.updateCellName({ cellId: firstCellId, name: "Test Cell" });
1755
- expect(state.cellData[firstCellId].name).toBe("Test Cell");
1756
- });
1757
-
1758
1490
  it("can fold and unfold all cells", () => {
1759
1491
  actions.foldAll();
1760
1492
  expect(foldAllBulk).toHaveBeenCalled();
@@ -1783,13 +1515,13 @@ describe("cell reducer", () => {
1783
1515
  });
1784
1516
 
1785
1517
  it("can collapse and expand cells", () => {
1786
- actions.createNewCell({ cellId: firstCellId, before: false });
1787
- actions.createNewCell({
1518
+ seedCell({ cellId: firstCellId, before: false });
1519
+ seedCell({
1788
1520
  cellId: cellId("1"),
1789
1521
  before: false,
1790
1522
  code: "# Header",
1791
1523
  });
1792
- actions.createNewCell({
1524
+ seedCell({
1793
1525
  cellId: cellId("2"),
1794
1526
  before: false,
1795
1527
  code: "## Subheader",
@@ -1811,13 +1543,13 @@ describe("cell reducer", () => {
1811
1543
  });
1812
1544
 
1813
1545
  it("can collapse and expand all cells in multiple columns", () => {
1814
- actions.createNewCell({ cellId: firstCellId, before: false });
1815
- actions.createNewCell({
1546
+ seedCell({ cellId: firstCellId, before: false });
1547
+ seedCell({
1816
1548
  cellId: cellId("1"),
1817
1549
  before: false,
1818
1550
  code: "# First Column Header",
1819
1551
  });
1820
- actions.createNewCell({
1552
+ seedCell({
1821
1553
  cellId: cellId("2"),
1822
1554
  before: false,
1823
1555
  code: "## First Column Subheader",
@@ -1825,12 +1557,12 @@ describe("cell reducer", () => {
1825
1557
 
1826
1558
  actions.addColumnBreakpoint({ cellId: cellId("2") });
1827
1559
 
1828
- actions.createNewCell({
1560
+ seedCell({
1829
1561
  cellId: cellId("3"),
1830
1562
  before: false,
1831
1563
  code: "# Second Column Header",
1832
1564
  });
1833
- actions.createNewCell({
1565
+ seedCell({
1834
1566
  cellId: cellId("4"),
1835
1567
  before: false,
1836
1568
  code: "## Second Column Subheader",
@@ -1878,18 +1610,18 @@ describe("cell reducer", () => {
1878
1610
  });
1879
1611
 
1880
1612
  it("can collapse and expand nested cells in one call", () => {
1881
- actions.createNewCell({ cellId: firstCellId, before: false });
1882
- actions.createNewCell({
1613
+ seedCell({ cellId: firstCellId, before: false });
1614
+ seedCell({
1883
1615
  cellId: cellId("1"),
1884
1616
  before: false,
1885
1617
  code: "# Header",
1886
1618
  });
1887
- actions.createNewCell({
1619
+ seedCell({
1888
1620
  cellId: cellId("2"),
1889
1621
  before: false,
1890
1622
  code: "## Subheader",
1891
1623
  });
1892
- actions.createNewCell({
1624
+ seedCell({
1893
1625
  cellId: cellId("3"),
1894
1626
  before: false,
1895
1627
  code: "### Subsubheader",
@@ -1933,18 +1665,18 @@ describe("cell reducer", () => {
1933
1665
  });
1934
1666
 
1935
1667
  it("can collapse all cells when nested heading ranges end at the same cell", () => {
1936
- actions.createNewCell({ cellId: firstCellId, before: false });
1937
- actions.createNewCell({
1668
+ seedCell({ cellId: firstCellId, before: false });
1669
+ seedCell({
1938
1670
  cellId: cellId("1"),
1939
1671
  before: false,
1940
1672
  code: "# Header",
1941
1673
  });
1942
- actions.createNewCell({
1674
+ seedCell({
1943
1675
  cellId: cellId("2"),
1944
1676
  before: false,
1945
1677
  code: "## Subheader",
1946
1678
  });
1947
- actions.createNewCell({
1679
+ seedCell({
1948
1680
  cellId: cellId("3"),
1949
1681
  before: false,
1950
1682
  code: "### Subsubheader",
@@ -1998,23 +1730,23 @@ describe("cell reducer", () => {
1998
1730
  });
1999
1731
 
2000
1732
  it("can collapse all cells when a deep chain is followed by a later section", () => {
2001
- actions.createNewCell({ cellId: firstCellId, before: false });
2002
- actions.createNewCell({
1733
+ seedCell({ cellId: firstCellId, before: false });
1734
+ seedCell({
2003
1735
  cellId: cellId("1"),
2004
1736
  before: false,
2005
1737
  code: "# Header",
2006
1738
  });
2007
- actions.createNewCell({
1739
+ seedCell({
2008
1740
  cellId: cellId("2"),
2009
1741
  before: false,
2010
1742
  code: "## Section A",
2011
1743
  });
2012
- actions.createNewCell({
1744
+ seedCell({
2013
1745
  cellId: cellId("3"),
2014
1746
  before: false,
2015
1747
  code: "### Sub of A",
2016
1748
  });
2017
- actions.createNewCell({
1749
+ seedCell({
2018
1750
  cellId: cellId("4"),
2019
1751
  before: false,
2020
1752
  code: "## Section B",
@@ -2077,8 +1809,8 @@ describe("cell reducer", () => {
2077
1809
  });
2078
1810
 
2079
1811
  it("can show hidden cells", () => {
2080
- actions.createNewCell({ cellId: firstCellId, before: false });
2081
- actions.createNewCell({ cellId: cellId("1"), before: false });
1812
+ seedCell({ cellId: firstCellId, before: false });
1813
+ seedCell({ cellId: cellId("1"), before: false });
2082
1814
  actions.collapseCell({ cellId: firstCellId });
2083
1815
 
2084
1816
  actions.showCellIfHidden({ cellId: cellId("1") });
@@ -2088,7 +1820,7 @@ describe("cell reducer", () => {
2088
1820
  });
2089
1821
 
2090
1822
  it("can split and undo split cells", () => {
2091
- actions.createNewCell({
1823
+ seedCell({
2092
1824
  cellId: firstCellId,
2093
1825
  before: false,
2094
1826
  code: "line1\nline2",
@@ -2155,9 +1887,9 @@ describe("cell reducer", () => {
2155
1887
  });
2156
1888
 
2157
1889
  it("can add a column breakpoint", () => {
2158
- actions.createNewCell({ cellId: firstCellId, before: false });
2159
- actions.createNewCell({ cellId: cellId("1"), before: false });
2160
- actions.createNewCell({ cellId: cellId("2"), before: false });
1890
+ seedCell({ cellId: firstCellId, before: false });
1891
+ seedCell({ cellId: cellId("1"), before: false });
1892
+ seedCell({ cellId: cellId("2"), before: false });
2161
1893
 
2162
1894
  expect(state.cellIds.getColumns().length).toBe(1);
2163
1895
  expect(formatCells(state)).toMatchInlineSnapshot(`
@@ -2202,16 +1934,16 @@ describe("cell reducer", () => {
2202
1934
 
2203
1935
  it("cannot add a column breakpoint before the first cell", () => {
2204
1936
  expect(state.cellIds.getColumns().length).toBe(1);
2205
- actions.createNewCell({ cellId: firstCellId, before: false });
2206
- actions.createNewCell({ cellId: cellId("1"), before: false });
1937
+ seedCell({ cellId: firstCellId, before: false });
1938
+ seedCell({ cellId: cellId("1"), before: false });
2207
1939
  actions.addColumnBreakpoint({ cellId: firstCellId });
2208
1940
  expect(state.cellIds.getColumns().length).toBe(1);
2209
1941
  });
2210
1942
 
2211
1943
  it("can delete a column", () => {
2212
- actions.createNewCell({ cellId: firstCellId, before: false });
2213
- actions.createNewCell({ cellId: cellId("1"), before: false });
2214
- actions.createNewCell({ cellId: cellId("2"), before: false });
1944
+ seedCell({ cellId: firstCellId, before: false });
1945
+ seedCell({ cellId: cellId("1"), before: false });
1946
+ seedCell({ cellId: cellId("2"), before: false });
2215
1947
  actions.addColumnBreakpoint({ cellId: cellId("2") });
2216
1948
 
2217
1949
  expect(state.cellIds.getColumns().length).toBe(2);
@@ -2255,8 +1987,8 @@ describe("cell reducer", () => {
2255
1987
  });
2256
1988
 
2257
1989
  it("deleting the last column does nothing", () => {
2258
- actions.createNewCell({ cellId: firstCellId, before: false });
2259
- actions.createNewCell({ cellId: cellId("1"), before: false });
1990
+ seedCell({ cellId: firstCellId, before: false });
1991
+ seedCell({ cellId: cellId("1"), before: false });
2260
1992
 
2261
1993
  const initialState = { ...state };
2262
1994
 
@@ -2267,9 +1999,9 @@ describe("cell reducer", () => {
2267
1999
  });
2268
2000
 
2269
2001
  it("can drop a cell over another cell", () => {
2270
- actions.createNewCell({ cellId: firstCellId, before: false });
2271
- actions.createNewCell({ cellId: cellId("1"), before: false });
2272
- actions.createNewCell({ cellId: cellId("2"), before: false });
2002
+ seedCell({ cellId: firstCellId, before: false });
2003
+ seedCell({ cellId: cellId("1"), before: false });
2004
+ seedCell({ cellId: cellId("2"), before: false });
2273
2005
 
2274
2006
  expect(formatCells(state)).toMatchInlineSnapshot(`
2275
2007
  "
@@ -2302,8 +2034,8 @@ describe("cell reducer", () => {
2302
2034
  });
2303
2035
 
2304
2036
  it("can drop a cell over a new column", () => {
2305
- actions.createNewCell({ cellId: firstCellId, before: false });
2306
- actions.createNewCell({ cellId: cellId("1"), before: false });
2037
+ seedCell({ cellId: firstCellId, before: false });
2038
+ seedCell({ cellId: cellId("1"), before: false });
2307
2039
 
2308
2040
  expect(state.cellIds.getColumns().length).toBe(1);
2309
2041
  expect(formatCells(state)).toMatchInlineSnapshot(`
@@ -2340,9 +2072,9 @@ describe("cell reducer", () => {
2340
2072
  });
2341
2073
 
2342
2074
  it("can drop a column over another column", () => {
2343
- actions.createNewCell({ cellId: firstCellId, before: false });
2344
- actions.createNewCell({ cellId: cellId("1"), before: false });
2345
- actions.createNewCell({ cellId: cellId("2"), before: false });
2075
+ seedCell({ cellId: firstCellId, before: false });
2076
+ seedCell({ cellId: cellId("1"), before: false });
2077
+ seedCell({ cellId: cellId("2"), before: false });
2346
2078
  actions.addColumnBreakpoint({ cellId: cellId("2") });
2347
2079
 
2348
2080
  expect(state.cellIds.getColumns().length).toBe(2);
@@ -2393,8 +2125,8 @@ describe("cell reducer", () => {
2393
2125
 
2394
2126
  it("can compact columns", () => {
2395
2127
  // Create initial state with 3 columns, including an empty one
2396
- actions.createNewCell({ cellId: firstCellId, before: false });
2397
- actions.createNewCell({ cellId: cellId("1"), before: false });
2128
+ seedCell({ cellId: firstCellId, before: false });
2129
+ seedCell({ cellId: cellId("1"), before: false });
2398
2130
  actions.addColumnBreakpoint({ cellId: cellId("1") });
2399
2131
  actions.addColumnBreakpoint({ cellId: cellId("2") });
2400
2132
  actions.dropOverNewColumn({ cellId: cellId("2") });
@@ -2533,7 +2265,7 @@ describe("cell reducer", () => {
2533
2265
 
2534
2266
  it("can clear output of all cells", () => {
2535
2267
  // Create multiple cells with output
2536
- actions.createNewCell({ cellId: firstCellId, before: false });
2268
+ seedCell({ cellId: firstCellId, before: false });
2537
2269
  const secondCellId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
2538
2270
 
2539
2271
  // Add output to both cells
@@ -2580,121 +2312,9 @@ describe("cell reducer", () => {
2580
2312
  expect(state.cellRuntime[secondCellId].consoleOutputs).toEqual([]);
2581
2313
  });
2582
2314
 
2583
- it("skips creating new cell if code exists and skipIfCodeExists is true", () => {
2584
- // Add initial cell with code
2585
- actions.updateCellCode({
2586
- cellId: firstCellId,
2587
- code: "import numpy as np",
2588
- formattingChange: false,
2589
- });
2590
-
2591
- // Try to create new cell with same code and skipIfCodeExists
2592
- actions.createNewCell({
2593
- cellId: "__end__",
2594
- code: "import numpy as np",
2595
- before: false,
2596
- skipIfCodeExists: true,
2597
- });
2598
-
2599
- // Should still only have one cell
2600
- expect(state.cellIds.inOrderIds.length).toBe(1);
2601
- expect(formatCells(state)).toMatchInlineSnapshot(`
2602
- "
2603
- [0] 'import numpy as np'
2604
- "
2605
- `);
2606
-
2607
- // Verify we can still add cell with different code
2608
- actions.createNewCell({
2609
- cellId: "__end__",
2610
- code: "import pandas as pd",
2611
- before: false,
2612
- skipIfCodeExists: true,
2613
- });
2614
-
2615
- expect(state.cellIds.inOrderIds.length).toBe(2);
2616
- expect(formatCells(state)).toMatchInlineSnapshot(`
2617
- "
2618
- [0] 'import numpy as np'
2619
-
2620
- [1] 'import pandas as pd'
2621
- "
2622
- `);
2623
- });
2624
-
2625
- it("can create and noop-update a setup cell", () => {
2626
- // Create the setup cell
2627
- actions.addSetupCellIfDoesntExist({ code: "# Setup code" });
2628
-
2629
- // Check that setup cell was created
2630
- expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
2631
- expect(state.cellData[SETUP_CELL_ID].name).toBe("setup");
2632
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
2633
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2634
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2635
-
2636
- // Update the setup cell
2637
- actions.addSetupCellIfDoesntExist({ code: "# Updated setup code" });
2638
-
2639
- // Check that the setup cell did not change, since it already exists
2640
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
2641
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2642
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2643
- });
2644
-
2645
- it("can delete and undelete the setup cell", () => {
2646
- // Create the setup cell
2647
- actions.addSetupCellIfDoesntExist({ code: "# Setup code" });
2648
-
2649
- // Check that setup cell was created
2650
- expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
2651
- expect(state.cellData[SETUP_CELL_ID].name).toBe("setup");
2652
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
2653
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2654
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2655
-
2656
- // Delete the setup cell
2657
- actions.deleteCell({ cellId: SETUP_CELL_ID });
2658
-
2659
- // Check that setup cell was deleted
2660
- expect(state.cellData[SETUP_CELL_ID]).toBeDefined(); // we keep old state
2661
- expect(state.cellIds.inOrderIds).not.toContain(SETUP_CELL_ID);
2662
-
2663
- // Undo delete the setup cell
2664
- actions.undoDeleteCell();
2665
-
2666
- // Check that setup cell was restored
2667
- expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
2668
- expect(state.cellData[SETUP_CELL_ID].name).toBe("setup");
2669
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
2670
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2671
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2672
-
2673
- // Verify scrollKey is set to the restored setup cell
2674
- expect(state.scrollKey).toBe(SETUP_CELL_ID);
2675
- });
2676
-
2677
- it("can delete and then create a new setup cell", () => {
2678
- // Create the setup cell
2679
- actions.addSetupCellIfDoesntExist({ code: "# Setup code" });
2680
-
2681
- // Delete the setup cell
2682
- actions.deleteCell({ cellId: SETUP_CELL_ID });
2683
-
2684
- // Create a new setup cell
2685
- actions.addSetupCellIfDoesntExist({ code: "# New code" });
2686
-
2687
- // Check that the new setup cell was created
2688
- expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
2689
- expect(state.cellData[SETUP_CELL_ID].name).toBe("setup");
2690
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# New code");
2691
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2692
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2693
- });
2694
-
2695
2315
  it("can clear all outputs", () => {
2696
2316
  // Add a cell and give it output
2697
- actions.createNewCell({
2317
+ seedCell({
2698
2318
  cellId: firstCellId,
2699
2319
  before: false,
2700
2320
  });
@@ -2722,202 +2342,14 @@ describe("cell reducer", () => {
2722
2342
  expect(state.cellRuntime[cell2Id].output).toBeNull();
2723
2343
  });
2724
2344
 
2725
- describe("moveToNextCell", () => {
2726
- let cell1Id: CellId;
2727
- let cell2Id: CellId;
2728
- let cell3Id: CellId;
2729
-
2730
- beforeEach(() => {
2731
- // Create a few cells to work with
2732
- actions.createNewCell({ cellId: "__end__", before: false });
2733
- actions.createNewCell({ cellId: "__end__", before: false });
2734
-
2735
- cell1Id = state.cellIds.inOrderIds[0];
2736
- cell2Id = state.cellIds.inOrderIds[1];
2737
- cell3Id = state.cellIds.inOrderIds[2];
2738
- });
2739
-
2740
- it("creates new cell when moving after last cell with noCreate=false", () => {
2741
- const initialCellCount = state.cellIds.inOrderIds.length;
2742
-
2743
- actions.moveToNextCell({
2744
- cellId: cell3Id,
2745
- before: false,
2746
- noCreate: false,
2747
- });
2748
-
2749
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount + 1);
2750
- });
2751
-
2752
- it("creates new cell when moving before first cell with noCreate=false", () => {
2753
- const initialCellCount = state.cellIds.inOrderIds.length;
2754
-
2755
- actions.moveToNextCell({
2756
- cellId: cell1Id,
2757
- before: true,
2758
- noCreate: false,
2759
- });
2760
-
2761
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount + 1);
2762
- });
2763
-
2764
- it("does not create new cell when moving after last cell with noCreate=true", () => {
2765
- const initialCellCount = state.cellIds.inOrderIds.length;
2766
- const initialState = { ...state };
2767
-
2768
- actions.moveToNextCell({
2769
- cellId: cell3Id,
2770
- before: false,
2771
- noCreate: true,
2772
- });
2773
-
2774
- // Should not create a new cell
2775
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount);
2776
- // Should not crash or throw an error
2777
- expect(state.cellIds.inOrderIds).toEqual(initialState.cellIds.inOrderIds);
2778
- });
2779
-
2780
- it("does not create new cell when moving before first cell with noCreate=true", () => {
2781
- const initialCellCount = state.cellIds.inOrderIds.length;
2782
- const initialState = { ...state };
2783
-
2784
- actions.moveToNextCell({ cellId: cell1Id, before: true, noCreate: true });
2785
-
2786
- // Should not create a new cell
2787
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount);
2788
- // Should not crash or throw an error
2789
- expect(state.cellIds.inOrderIds).toEqual(initialState.cellIds.inOrderIds);
2790
- });
2791
-
2792
- it("focuses next cell when moving within bounds", () => {
2793
- const focusSpy = vi.mocked(focusAndScrollCellIntoView);
2794
- focusSpy.mockClear();
2795
-
2796
- actions.moveToNextCell({
2797
- cellId: cell1Id,
2798
- before: false,
2799
- noCreate: true,
2800
- });
2801
-
2802
- expect(focusSpy).toHaveBeenCalledWith({
2803
- cellId: cell2Id,
2804
- cell: state.cellHandles[cell2Id],
2805
- isCodeHidden: false,
2806
- codeFocus: "top",
2807
- variableName: undefined,
2808
- });
2809
- });
2810
-
2811
- it("focuses previous cell when moving backward within bounds", () => {
2812
- const focusSpy = vi.mocked(focusAndScrollCellIntoView);
2813
- focusSpy.mockClear();
2814
-
2815
- actions.moveToNextCell({ cellId: cell2Id, before: true, noCreate: true });
2816
-
2817
- expect(focusSpy).toHaveBeenCalledWith({
2818
- cellId: cell1Id,
2819
- cell: state.cellHandles[cell1Id],
2820
- isCodeHidden: false,
2821
- codeFocus: "bottom",
2822
- variableName: undefined,
2823
- });
2824
- });
2825
-
2826
- it("does not move focus from scratch cell", () => {
2827
- const focusSpy = vi.mocked(focusAndScrollCellIntoView);
2828
- focusSpy.mockClear();
2829
- const initialState = { ...state };
2830
-
2831
- actions.moveToNextCell({
2832
- cellId: SCRATCH_CELL_ID,
2833
- before: false,
2834
- noCreate: false,
2835
- });
2836
-
2837
- expect(focusSpy).not.toHaveBeenCalled();
2838
- expect(state.cellIds.inOrderIds).toEqual(initialState.cellIds.inOrderIds);
2839
- });
2840
- });
2841
-
2842
2345
  describe("untouched cells functionality", () => {
2843
2346
  it("starts with empty untouchedNewCells set", () => {
2844
2347
  expect(state.untouchedNewCells).toEqual(new Set());
2845
2348
  });
2846
2349
 
2847
- it("can create a new cell with hideCode option", () => {
2848
- const initialCellCount = state.cellIds.inOrderIds.length;
2849
-
2850
- actions.createNewCell({
2851
- cellId: "__end__",
2852
- before: false,
2853
- hideCode: true,
2854
- });
2855
-
2856
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount + 1);
2857
- const newCellId =
2858
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
2859
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
2860
- });
2861
-
2862
- it("adds interactively-created hidden cell with boilerplate code to untouchedNewCells", () => {
2863
- // Markdown cells are created with hideCode and non-empty default code
2864
- // (e.g. `mo.md(r"""\n""")`). They are user-initiated, so their editor
2865
- // should be shown until first blur.
2866
- actions.createNewCell({
2867
- cellId: "__end__",
2868
- before: false,
2869
- code: 'mo.md(r"""\n""")',
2870
- hideCode: true,
2871
- autoFocus: true,
2872
- });
2873
-
2874
- const newCellId =
2875
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
2876
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
2877
- });
2878
-
2879
- it("does not add programmatically-created hidden cell with code to untouchedNewCells", () => {
2880
- // Cells created by the kernel (e.g. via code_mode) carry code and
2881
- // autoFocus=false; their hide_code must take effect immediately.
2882
- actions.createNewCell({
2883
- cellId: "__end__",
2884
- before: false,
2885
- code: "x = 1",
2886
- hideCode: true,
2887
- autoFocus: false,
2888
- });
2889
-
2890
- const newCellId =
2891
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
2892
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
2893
- });
2894
-
2895
- it("does not add cell to untouchedNewCells when hideCode is false", () => {
2896
- actions.createNewCell({
2897
- cellId: "__end__",
2898
- before: false,
2899
- hideCode: false,
2900
- });
2901
-
2902
- const newCellId =
2903
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
2904
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
2905
- });
2906
-
2907
- it("does not add cell to untouchedNewCells when hideCode is undefined", () => {
2908
- actions.createNewCell({
2909
- cellId: "__end__",
2910
- before: false,
2911
- });
2912
-
2913
- const newCellId =
2914
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
2915
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
2916
- });
2917
-
2918
2350
  it("can mark a cell as touched", () => {
2919
2351
  // Create a cell with hideCode
2920
- actions.createNewCell({
2352
+ seedCell({
2921
2353
  cellId: "__end__",
2922
2354
  before: false,
2923
2355
  hideCode: true,
@@ -2935,7 +2367,7 @@ describe("cell reducer", () => {
2935
2367
 
2936
2368
  it("markTouched is idempotent", () => {
2937
2369
  // Create a cell with hideCode
2938
- actions.createNewCell({
2370
+ seedCell({
2939
2371
  cellId: "__end__",
2940
2372
  before: false,
2941
2373
  hideCode: true,
@@ -2965,17 +2397,17 @@ describe("cell reducer", () => {
2965
2397
 
2966
2398
  it("can handle multiple untouched cells", () => {
2967
2399
  // Create multiple cells with hideCode
2968
- actions.createNewCell({
2400
+ seedCell({
2969
2401
  cellId: "__end__",
2970
2402
  before: false,
2971
2403
  hideCode: true,
2972
2404
  });
2973
- actions.createNewCell({
2405
+ seedCell({
2974
2406
  cellId: "__end__",
2975
2407
  before: false,
2976
2408
  hideCode: true,
2977
2409
  });
2978
- actions.createNewCell({
2410
+ seedCell({
2979
2411
  cellId: "__end__",
2980
2412
  before: false,
2981
2413
  hideCode: false,
@@ -3000,12 +2432,12 @@ describe("cell reducer", () => {
3000
2432
 
3001
2433
  it("preserves untouched state when cells are moved", () => {
3002
2434
  // Create cells with hideCode
3003
- actions.createNewCell({
2435
+ seedCell({
3004
2436
  cellId: "__end__",
3005
2437
  before: false,
3006
2438
  hideCode: true,
3007
2439
  });
3008
- actions.createNewCell({
2440
+ seedCell({
3009
2441
  cellId: "__end__",
3010
2442
  before: false,
3011
2443
  hideCode: true,
@@ -3029,58 +2461,9 @@ describe("cell reducer", () => {
3029
2461
  expect(state.untouchedNewCells.has(cell2Id)).toBe(true);
3030
2462
  });
3031
2463
 
3032
- it("does not remove untouched state when cell is deleted", () => {
3033
- // We could implement this, but it doesn't actually affect downstream behavior.
3034
- // It is easier to leave it, so `undo` works as expected.
3035
-
3036
- // Create a cell with hideCode
3037
- actions.createNewCell({
3038
- cellId: "__end__",
3039
- before: false,
3040
- hideCode: true,
3041
- });
3042
-
3043
- const newCellId =
3044
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3045
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3046
-
3047
- // Delete the cell
3048
- actions.deleteCell({ cellId: newCellId });
3049
-
3050
- // Does not actually remove untouched state
3051
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3052
- });
3053
-
3054
- it("restores untouched state when cell deletion is undone", () => {
3055
- // Create a cell with hideCode
3056
- actions.createNewCell({
3057
- cellId: "__end__",
3058
- before: false,
3059
- hideCode: true,
3060
- });
3061
-
3062
- const newCellId =
3063
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3064
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3065
-
3066
- // Delete the cell
3067
- actions.deleteCell({ cellId: newCellId });
3068
- // Still exists in untouchedNewCells
3069
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3070
-
3071
- // Undo the deletion
3072
- actions.undoDeleteCell();
3073
-
3074
- // The cell should be restored but no longer untouched
3075
- // (this is expected behavior - undoing doesn't restore untouched state)
3076
- const restoredCellId =
3077
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3078
- expect(state.untouchedNewCells.has(restoredCellId)).toBe(false);
3079
- });
3080
-
3081
2464
  it("untouched cell behavior with hide_code config", () => {
3082
2465
  // Create a cell with hideCode and hide_code config
3083
- actions.createNewCell({
2466
+ seedCell({
3084
2467
  cellId: "__end__",
3085
2468
  before: false,
3086
2469
  hideCode: true,
@@ -3089,11 +2472,11 @@ describe("cell reducer", () => {
3089
2472
  const newCellId =
3090
2473
  state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3091
2474
 
3092
- // Update the cell config to hide code
3093
- actions.updateCellConfig({
3094
- cellId: newCellId,
3095
- config: { hide_code: true },
3096
- });
2475
+ // The document hides the cell's code
2476
+ state.cellData[newCellId] = {
2477
+ ...state.cellData[newCellId],
2478
+ config: { ...state.cellData[newCellId].config, hide_code: true },
2479
+ };
3097
2480
 
3098
2481
  // Cell should be untouched and have hide_code config
3099
2482
  expect(state.untouchedNewCells.has(newCellId)).toBe(true);
@@ -3112,7 +2495,7 @@ describe("cell reducer", () => {
3112
2495
 
3113
2496
  it("can mark an existing cell as untouched", () => {
3114
2497
  // Create a cell without hideCode (not in untouchedNewCells)
3115
- actions.createNewCell({
2498
+ seedCell({
3116
2499
  cellId: "__end__",
3117
2500
  before: false,
3118
2501
  hideCode: false,
@@ -3130,7 +2513,7 @@ describe("cell reducer", () => {
3130
2513
 
3131
2514
  it("markUntouched is idempotent", () => {
3132
2515
  // Create a cell without hideCode
3133
- actions.createNewCell({
2516
+ seedCell({
3134
2517
  cellId: "__end__",
3135
2518
  before: false,
3136
2519
  hideCode: false,
@@ -3150,7 +2533,7 @@ describe("cell reducer", () => {
3150
2533
 
3151
2534
  it("markUntouched does not affect already untouched cells", () => {
3152
2535
  // Create a cell with hideCode (already in untouchedNewCells)
3153
- actions.createNewCell({
2536
+ seedCell({
3154
2537
  cellId: "__end__",
3155
2538
  before: false,
3156
2539
  hideCode: true,
@@ -3168,7 +2551,7 @@ describe("cell reducer", () => {
3168
2551
 
3169
2552
  it("markTouched and markUntouched can toggle cell state", () => {
3170
2553
  // Create a cell without hideCode
3171
- actions.createNewCell({
2554
+ seedCell({
3172
2555
  cellId: "__end__",
3173
2556
  before: false,
3174
2557
  hideCode: false,
@@ -3196,7 +2579,7 @@ describe("cell reducer", () => {
3196
2579
  it("markUntouched works for markdown cell conversion scenario", () => {
3197
2580
  // Simulates converting a Python cell to Markdown
3198
2581
  // 1. Create a regular cell (no hideCode)
3199
- actions.createNewCell({
2582
+ seedCell({
3200
2583
  cellId: "__end__",
3201
2584
  before: false,
3202
2585
  hideCode: false,
@@ -3210,11 +2593,12 @@ describe("cell reducer", () => {
3210
2593
  expect(state.untouchedNewCells.has(cellId)).toBe(false);
3211
2594
  expect(exportedForTesting.isCellCodeHidden(state, cellId)).toBe(false);
3212
2595
 
3213
- // 2. Convert to markdown: set hide_code and mark as untouched
3214
- actions.updateCellConfig({
3215
- cellId,
3216
- config: { hide_code: true },
3217
- });
2596
+ // 2. Convert to markdown: the document hides the code, and the cell
2597
+ // is marked untouched
2598
+ state.cellData[cellId] = {
2599
+ ...state.cellData[cellId],
2600
+ config: { ...state.cellData[cellId].config, hide_code: true },
2601
+ };
3218
2602
  actions.markUntouched({ cellId });
3219
2603
 
3220
2604
  // Code should NOT be hidden because cell is untouched (user can edit)
@@ -3236,7 +2620,7 @@ describe("cell reducer", () => {
3236
2620
  const { cellDataAtom, cellRuntimeAtom, cellHandleAtom } =
3237
2621
  exportedForTesting;
3238
2622
 
3239
- actions.createNewCell({ cellId: firstCellId, before: false });
2623
+ seedCell({ cellId: firstCellId, before: false });
3240
2624
  const newCellId = state.cellIds.inOrderIds[1];
3241
2625
 
3242
2626
  const dataAtom1 = cellDataAtom(newCellId);
@@ -3252,31 +2636,6 @@ describe("cell reducer", () => {
3252
2636
  expect(runtimeAtom2).toBe(runtimeAtom1);
3253
2637
  expect(handleAtom2).toBe(handleAtom1);
3254
2638
  });
3255
-
3256
- it("cleans up atom family cache when cells are deleted", () => {
3257
- const { cellDataAtom, cellRuntimeAtom, cellHandleAtom } =
3258
- exportedForTesting;
3259
-
3260
- actions.createNewCell({ cellId: firstCellId, before: false });
3261
- const newCellId = state.cellIds.inOrderIds[1];
3262
-
3263
- // Access to create cache entries
3264
- const dataAtom1 = cellDataAtom(newCellId);
3265
- const runtimeAtom1 = cellRuntimeAtom(newCellId);
3266
- const handleAtom1 = cellHandleAtom(newCellId);
3267
-
3268
- // Triggers purge
3269
- actions.deleteCell({ cellId: newCellId });
3270
-
3271
- // Access again (should be new instances after cleanup)
3272
- const dataAtom2 = cellDataAtom(newCellId);
3273
- const runtimeAtom2 = cellRuntimeAtom(newCellId);
3274
- const handleAtom2 = cellHandleAtom(newCellId);
3275
-
3276
- expect(dataAtom2).not.toBe(dataAtom1);
3277
- expect(runtimeAtom2).not.toBe(runtimeAtom1);
3278
- expect(handleAtom2).not.toBe(handleAtom1);
3279
- });
3280
2639
  });
3281
2640
  });
3282
2641
 
@@ -3591,31 +2950,9 @@ describe("createTracebackInfoAtom", () => {
3591
2950
  });
3592
2951
  });
3593
2952
 
3594
- describe("setCells snapshot preservation", () => {
2953
+ describe("kernel-ready", () => {
3595
2954
  const CELL_A = cellId("A");
3596
2955
  const CELL_B = cellId("B");
3597
- const newCells: CellData[] = [
3598
- {
3599
- id: CELL_A,
3600
- name: "a",
3601
- code: "1",
3602
- edited: false,
3603
- lastCodeRun: null,
3604
- lastExecutionTime: null,
3605
- config: { hide_code: false, disabled: false, column: null },
3606
- serializedEditorState: null,
3607
- },
3608
- {
3609
- id: CELL_B,
3610
- name: "b",
3611
- code: "2",
3612
- edited: false,
3613
- lastCodeRun: null,
3614
- lastExecutionTime: null,
3615
- config: { hide_code: false, disabled: false, column: null },
3616
- serializedEditorState: null,
3617
- },
3618
- ];
3619
2956
 
3620
2957
  const hydratedState = () =>
3621
2958
  MockNotebook.notebookState({
@@ -3645,44 +2982,97 @@ describe("setCells snapshot preservation", () => {
3645
2982
  },
3646
2983
  });
3647
2984
 
3648
- beforeEach(async () => {
3649
- const { isWasm } = await import("@/core/wasm/utils");
3650
- vi.mocked(isWasm).mockReturnValue(true);
2985
+ it("setLastRun records what the kernel last ran", () => {
2986
+ const state = hydratedState();
2987
+ const next = exportedForTesting.reducer(state, {
2988
+ type: "setLastRun",
2989
+ payload: {
2990
+ lastCodeRun: { [CELL_A]: "0" },
2991
+ lastExecutionTime: { [CELL_A]: 5 },
2992
+ },
2993
+ });
2994
+
2995
+ expect(next.cellData[CELL_A]).toMatchObject({
2996
+ lastCodeRun: "0",
2997
+ lastExecutionTime: 5,
2998
+ edited: true,
2999
+ });
3000
+ expect(next.cellData[CELL_B]).toMatchObject({
3001
+ lastCodeRun: null,
3002
+ lastExecutionTime: null,
3003
+ edited: false,
3004
+ });
3005
+ expect(next.cellRuntime).toBe(state.cellRuntime);
3651
3006
  });
3652
3007
 
3653
- it("preserves hydrated output in WASM", () => {
3008
+ it("resetCellRuntimes starts every cell's runtime over", () => {
3654
3009
  const next = exportedForTesting.reducer(hydratedState(), {
3655
- type: "setCells",
3656
- payload: newCells,
3010
+ type: "resetCellRuntimes",
3011
+ payload: undefined,
3657
3012
  });
3658
3013
 
3659
- expect(next.cellRuntime[CELL_A].output).toMatchObject({
3660
- data: "hydrated-A",
3014
+ expect(next.cellRuntime[CELL_A].output).toBeNull();
3015
+ expect(next.cellRuntime[CELL_B].consoleOutputs).toEqual([]);
3016
+ });
3017
+
3018
+ it("resetCellRuntimes starts the scratch cell over too", () => {
3019
+ const state = MockNotebook.notebookState({
3020
+ cellData: { [CELL_A]: { id: CELL_A, code: "1" } },
3021
+ cellRuntime: {
3022
+ [SCRATCH_CELL_ID]: {
3023
+ output: {
3024
+ channel: "output",
3025
+ mimetype: "text/plain",
3026
+ data: "scratch",
3027
+ timestamp: 0,
3028
+ },
3029
+ },
3030
+ },
3661
3031
  });
3032
+ const next = exportedForTesting.reducer(state, {
3033
+ type: "resetCellRuntimes",
3034
+ payload: undefined,
3035
+ });
3036
+
3037
+ expect(next.cellRuntime[SCRATCH_CELL_ID].output).toBeNull();
3662
3038
  });
3663
3039
 
3664
- it("preserves console-only hydration in WASM", () => {
3665
- const next = exportedForTesting.reducer(hydratedState(), {
3666
- type: "setCells",
3667
- payload: newCells,
3040
+ describe("resetCellRuntimes in WASM", () => {
3041
+ beforeEach(async () => {
3042
+ const { isWasm } = await import("@/core/wasm/utils");
3043
+ vi.mocked(isWasm).mockReturnValue(true);
3668
3044
  });
3669
3045
 
3670
- expect(next.cellRuntime[CELL_B].consoleOutputs).toHaveLength(1);
3671
- expect(next.cellRuntime[CELL_B].consoleOutputs[0]).toMatchObject({
3672
- data: "hydrated-B-stdout",
3046
+ afterEach(async () => {
3047
+ const { isWasm } = await import("@/core/wasm/utils");
3048
+ vi.mocked(isWasm).mockReturnValue(false);
3673
3049
  });
3674
- });
3675
3050
 
3676
- it("resets cells with no prior runtime even in WASM", () => {
3677
- const empty = MockNotebook.notebookState({ cellData: {} });
3678
- const next = exportedForTesting.reducer(empty, {
3679
- type: "setCells",
3680
- payload: newCells,
3051
+ it("keeps hydrated outputs and console", () => {
3052
+ const next = exportedForTesting.reducer(hydratedState(), {
3053
+ type: "resetCellRuntimes",
3054
+ payload: undefined,
3055
+ });
3056
+
3057
+ expect(next.cellRuntime[CELL_A].output).toMatchObject({
3058
+ data: "hydrated-A",
3059
+ });
3060
+ expect(next.cellRuntime[CELL_B].consoleOutputs[0]).toMatchObject({
3061
+ data: "hydrated-B-stdout",
3062
+ });
3681
3063
  });
3682
3064
 
3683
- expect(next.cellRuntime[CELL_A].output).toBeNull();
3684
- expect(next.cellRuntime[CELL_A].consoleOutputs).toEqual([]);
3685
- expect(next.cellRuntime[CELL_B].output).toBeNull();
3686
- expect(next.cellRuntime[CELL_B].consoleOutputs).toEqual([]);
3065
+ it("starts cells with no prior runtime over", () => {
3066
+ const state = MockNotebook.notebookState({
3067
+ cellData: { [CELL_A]: { id: CELL_A, code: "1" } },
3068
+ });
3069
+ const next = exportedForTesting.reducer(state, {
3070
+ type: "resetCellRuntimes",
3071
+ payload: undefined,
3072
+ });
3073
+
3074
+ expect(next.cellRuntime[CELL_A].output).toBeNull();
3075
+ expect(next.cellRuntime[CELL_A].consoleOutputs).toEqual([]);
3076
+ });
3687
3077
  });
3688
3078
  });