@marimo-team/islands 0.25.3-dev19 → 0.25.3-dev191

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 (627) hide show
  1. package/dist/{ConnectedDataExplorerComponent-CNMliVzw.js → ConnectedDataExplorerComponent-BBbOElk3.js} +21 -19
  2. package/dist/{ErrorBoundary-CBHB755J.js → ErrorBoundary--X0z83Yx.js} +19 -19
  3. package/dist/{abnfDiagram-VCTEODGH-D-Ez91gV.js → abnfDiagram-VCTEODGH-DhMT6Uhd.js} +2 -2
  4. package/dist/any-language-editor-BVFHDDQ3.js +75 -0
  5. package/dist/{architectureDiagram-5GKGNRK7-CTpcNtO8.js → architectureDiagram-5GKGNRK7-BVbEiJLq.js} +1 -1
  6. package/dist/{arrays-CN1djC4k.js → arrays-R6JsImiT.js} +8 -0
  7. package/dist/assets/__vite-browser-external-C8gQho07.js +1 -0
  8. package/dist/assets/worker-C6e9ERyd.js +113 -0
  9. package/dist/{button-CvjkwdSk.js → button-BQRdqQi1.js} +199 -192
  10. package/dist/{capabilities-CQ5IjhzL.js → capabilities-CI4xUPsp.js} +2 -2
  11. package/dist/{chat-ui-DaHynk8g.js → chat-ui-BguxTQEL.js} +541 -540
  12. package/dist/{check-EDwSwP89.js → check-C9Mp9eCK.js} +1 -1
  13. package/dist/{chunk-IMKFNOWR-DGxi-zeo.js → chunk-IMKFNOWR-By5hSbJJ.js} +1 -1
  14. package/dist/{chunk-SHT3W25Y-CV9J3rAB.js → chunk-SHT3W25Y-DFrEYG54.js} +1 -1
  15. package/dist/{chunk-SVP7TREG-BL-9Fqgg.js → chunk-SVP7TREG-DFXGc9ew.js} +1 -1
  16. package/dist/{chunk-TICWLB2K-Cu_XMjDM.js → chunk-TICWLB2K-DZdNIpoZ.js} +1 -1
  17. package/dist/{chunk-YOKDWASO-DPZk8vhS.js → chunk-YOKDWASO-DKplTrBY.js} +20 -20
  18. package/dist/{classDiagram-ZZMXUADV-BXXV13Hq.js → classDiagram-ZZMXUADV-Cgj_eEQA.js} +2 -2
  19. package/dist/{classDiagram-v2-VYDZK3BY-B7FXJ1Cp.js → classDiagram-v2-VYDZK3BY-DLAUrSqj.js} +2 -2
  20. package/dist/{code-visibility-CEXXuuOE.js → code-visibility-uDyLo9_i.js} +25 -25
  21. package/dist/{common-OLE92qsR.js → common-DWKY45Md.js} +6994 -6819
  22. package/dist/{copy-icon-Ft7t9bqz.js → copy-icon-De584pZ4.js} +3 -3
  23. package/dist/{cynefinDiagram-5FMLGOSQ-CH96rwba.js → cynefinDiagram-5FMLGOSQ-Cikf9AKa.js} +1 -1
  24. package/dist/{dagre-GXQ25YYZ-BD3PmpFY.js → dagre-GXQ25YYZ-C5KEi5_l.js} +1 -1
  25. package/dist/{data-grid-overlay-editor-CpHX98zN.js → data-grid-overlay-editor-CRnYWD1L.js} +5 -5
  26. package/dist/{data-grid-types-BH_r7g7l.js → data-grid-types-0PJ-9N5n.js} +1 -1
  27. package/dist/{diagram-S7CK7UJ4-BsSJJldB.js → diagram-S7CK7UJ4-BaoBTQjR.js} +1 -1
  28. package/dist/{diagram-UQ7AKVKN-muQtNZc4.js → diagram-UQ7AKVKN-DDufBsUA.js} +1 -1
  29. package/dist/{diagram-VX7I27RA-CdhuZAqR.js → diagram-VX7I27RA-CY6RALJ8.js} +1 -1
  30. package/dist/{diagram-Z3DM3KII-BD8mXjOj.js → diagram-Z3DM3KII-Bc-LHsD6.js} +1 -1
  31. package/dist/{dialog-CEBFZNyc.js → dialog-DVRfQuhD.js} +25 -25
  32. package/dist/{dist-F8Ep36rk.js → dist-B1M0bGO3.js} +1 -1
  33. package/dist/{dist-D6d86eNJ.js → dist-BA13RJj5.js} +1 -1
  34. package/dist/{dist-Bvs24R_E.js → dist-BJtmSgcE.js} +48 -48
  35. package/dist/{dist-CN_BeG2I.js → dist-Ba2VIn1K.js} +53 -53
  36. package/dist/{dist-PXYI_yuP.js → dist-Bj4bmwUm.js} +12 -12
  37. package/dist/{dist-DoE4XJBC.js → dist-BmS-ZGjl.js} +8626 -8626
  38. package/dist/{dist-BorMD-uX.js → dist-Bzogh7CW.js} +87 -87
  39. package/dist/{dist-CN8zI-XC.js → dist-CThPVKaP.js} +1385 -1382
  40. package/dist/{dist-DiDibOYr.js → dist-CTi5FTiV.js} +87 -87
  41. package/dist/{dist-diIx5qJW.js → dist-Ck_C32ye.js} +2 -2
  42. package/dist/{dist-BLfKmR-l.js → dist-D1fnFp1J.js} +1 -1
  43. package/dist/{dist-OR1OnN-y.js → dist-D3x9__Fu.js} +46 -46
  44. package/dist/{dist-BMQf5zIS.js → dist-D7mrh5sO.js} +36 -36
  45. package/dist/{dist-CSJ0MDRw.js → dist-D9Q3xjiP.js} +1 -1
  46. package/dist/{dist-BuaRHxOc.js → dist-DP60Ubh8.js} +2 -2
  47. package/dist/{dist-CSbh1PNP.js → dist-DTv9KkpW.js} +12 -12
  48. package/dist/{dist-D-wwqlzJ.js → dist-DVVwOfrD.js} +109 -109
  49. package/dist/{dist-C8fAKSH1.js → dist-DYXM73-k.js} +36 -36
  50. package/dist/{dist-DBtSl3sf.js → dist-DjeybPOb.js} +1 -1
  51. package/dist/{dist-CrSP_H1M.js → dist-DtGn9N4h.js} +197 -197
  52. package/dist/{dist-CLdpGQVr.js → dist-KN80qWCP.js} +2 -2
  53. package/dist/{dist-DpBpA_n1.js → dist-Lm-D-Vr6.js} +3 -3
  54. package/dist/{dist-Cg7gzw-a.js → dist-O0qFpEJh.js} +56 -56
  55. package/dist/{dist-CkTTIdBe.js → dist-OUle2RT7.js} +207 -207
  56. package/dist/{dist-BK4Oj99o.js → dist-T5rh6JvL.js} +1 -1
  57. package/dist/{dropdown-menu-CP_qCYHC.js → dropdown-menu-drDjxJyV.js} +1481 -1433
  58. package/dist/{ebnfDiagram-PWID7BFC-BIN1sweE.js → ebnfDiagram-PWID7BFC-Cz0J-tcC.js} +2 -2
  59. package/dist/{erDiagram-RLTQ6QDP-Ctp5cfoN.js → erDiagram-RLTQ6QDP-DTXTDgFu.js} +1 -1
  60. package/dist/{error-banner-D-WCIiMt.js → error-banner-DxJ3s8YO.js} +86 -86
  61. package/dist/{error-entries-Bv6bZPF3.js → error-entries-CZ1NQ6Kv.js} +4771 -4702
  62. package/dist/{esm-D2gTsXEV.js → esm-Bbw_Njjx.js} +575 -575
  63. package/dist/{esm-AMFssoNp.js → esm-mDQNEFe_.js} +354 -354
  64. package/dist/{feedback-button-_f7fx7zj.js → feedback-button-CDf1yRm_.js} +37 -37
  65. package/dist/{flowDiagram-HODETNUW-3EfLrlFb.js → flowDiagram-HODETNUW-RbwWf2zc.js} +2 -2
  66. package/dist/{formats-27CW_UP4.js → formats-DbXnijtH.js} +3 -3
  67. package/dist/{ganttDiagram-EL5Y4UJY-BQT16JSc.js → ganttDiagram-EL5Y4UJY-Dh_4cBJZ.js} +2 -2
  68. package/dist/{glide-data-editor-Cmu4vWKD.js → glide-data-editor-CvRiA49N.js} +370 -368
  69. package/dist/{glide-utils-DJyqh0EM.js → glide-utils-D98laidb.js} +16 -16
  70. package/dist/{globals-1Hfhz6Dx.js → globals-BR4CmGOS.js} +3 -3
  71. package/dist/{highlighted-body-KPVGNVTW-zwjq21tf.js → highlighted-body-KPVGNVTW-3Ziwfz-V.js} +2 -2
  72. package/dist/{html-to-image-Bf8QMGi4.js → html-to-image-D_4_hQ8U.js} +668 -622
  73. package/dist/{infoDiagram-27XIBGKW-DipQoHxl.js → infoDiagram-27XIBGKW-DlmzZjmI.js} +1 -1
  74. package/dist/{ishikawaDiagram-5VMMS53U-DgFMjSGz.js → ishikawaDiagram-5VMMS53U-EN3TYP_q.js} +1 -1
  75. package/dist/{kanban-definition-UXKFOSKX-w1ACVjgl.js → kanban-definition-UXKFOSKX-CK_-Ru7Q.js} +1 -1
  76. package/dist/{label-BDKF3Afc.js → label-Qvw1E97W.js} +3 -3
  77. package/dist/{linear-Cnaq3Tfb.js → linear-DWERh7eI.js} +121 -73
  78. package/dist/{loader-BQOfjTIJ.js → loader-DOVB0Syl.js} +12 -12
  79. package/dist/main.js +9822 -7824
  80. package/dist/{mermaid-CHo49Yv-.js → mermaid-DU9ZRmkG.js} +38 -38
  81. package/dist/mermaid-HWGCJPDP-Du3-HPQA.js +2 -0
  82. package/dist/{mindmap-definition-YA3MSWOX-CrT0KSfX.js → mindmap-definition-YA3MSWOX-C-hqgUvF.js} +1 -1
  83. package/dist/{number-overlay-editor-BQN3-Nye.js → number-overlay-editor-DiYGbxNa.js} +3 -3
  84. package/dist/{objects-CzUgoMIF.js → objects-DCmMvZtn.js} +1 -3
  85. package/dist/{once-BtE-wKRL.js → once-eG8Vmvjd.js} +1 -1
  86. package/dist/{pegDiagram-XKGWAZYB-BqvywNJA.js → pegDiagram-XKGWAZYB-BiMejYPB.js} +2 -2
  87. package/dist/{pieDiagram-E7YTZNPT-BfxJ_XCc.js → pieDiagram-E7YTZNPT-B1zCZ1eq.js} +1 -1
  88. package/dist/precisionRound-DhAzeRcm.js +49 -0
  89. package/dist/{process-output-BZg56CCb.js → process-output-DxSCHBWp.js} +4 -4
  90. package/dist/{quadrantDiagram-AXDQQJYC-Ccw0xMjN.js → quadrantDiagram-AXDQQJYC-Blrm7V7_.js} +1 -1
  91. package/dist/{railroadDiagram-O6MQD6OU-8vFLVTPu.js → railroadDiagram-O6MQD6OU-BLcfaASl.js} +2 -2
  92. package/dist/{react-vega-BED0DjiB.js → react-vega-C4T9Rb_w.js} +12284 -12270
  93. package/dist/{requirementDiagram-BXWQKSXE-a_MxGvxe.js → requirementDiagram-BXWQKSXE-DtBOf52-.js} +1 -1
  94. package/dist/{reveal-component-nqUW7zgn.js → reveal-component-D3DsXPIm.js} +734 -711
  95. package/dist/{select-BButFi-d.js → select-2KOiReNY.js} +536 -457
  96. package/dist/{sequenceDiagram-WJ2MYXX4-CXFMnxGh.js → sequenceDiagram-WJ2MYXX4-DIwF19_n.js} +1 -1
  97. package/dist/{spec-BwarBpUJ.js → spec-DZDnnlgT.js} +41 -11
  98. package/dist/{stateDiagram-D77RDMKH-DQeA_CwI.js → stateDiagram-D77RDMKH-sWk1AdYL.js} +2 -2
  99. package/dist/{stateDiagram-v2-MP3YSRHH-BxxQucm1.js → stateDiagram-v2-MP3YSRHH-CiNo-OI7.js} +2 -2
  100. package/dist/style.css +1 -1
  101. package/dist/{swimlanes-42K2YHIH-Do6L-cYg.js → swimlanes-42K2YHIH-Bj3hiOY5.js} +1 -1
  102. package/dist/{swimlanesDiagram-VR7AAH4N-DUzPvSVU.js → swimlanesDiagram-VR7AAH4N-BzEo2r_N.js} +2 -2
  103. package/dist/{swiper-component-DKntyn8p.js → swiper-component-BkCm-kLg.js} +2 -2
  104. package/dist/{time-BieKwdYS.js → time-BaMbJs4z.js} +18 -18
  105. package/dist/{timeline-definition-24CTP7MA-D3c46WM5.js → timeline-definition-24CTP7MA-CrPQk42H.js} +1 -1
  106. package/dist/{toDate-UtiwmnH2.js → toDate-Cu2pIZcC.js} +37 -36
  107. package/dist/{toast-B9UMEi8u.js → toast-B5t9o6s_.js} +219 -201
  108. package/dist/{tooltip-BO1REbqs.js → tooltip-s4zZxwKe.js} +325 -301
  109. package/dist/{use-restore-focus-BEM7sFWl.js → use-restore-focus-BgDTd2tc.js} +1482 -1433
  110. package/dist/{useAsyncData-Deh5kszT.js → useAsyncData-DEhLdRIu.js} +15 -14
  111. package/dist/{useDateFormatter-D8XruNGG.js → useDateFormatter-D3Tkfzej.js} +2 -2
  112. package/dist/{useDeepCompareMemoize-DXwzRINI.js → useDeepCompareMemoize-RjZGVhBV.js} +1 -1
  113. package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities-CSdWn6NP.js} +1 -1
  114. package/dist/{useLifecycle-j7D9uD1o.js → useLifecycle-DtcB2s1z.js} +28 -25
  115. package/dist/{useTheme-CRnjkku0.js → useTheme-CoEtr4mK.js} +301 -297
  116. package/dist/{vega-component-BeF9dfyO.js → vega-component-BhjTrP9-.js} +148 -148
  117. package/dist/{vega-embed-container-BNHE18G9.js → vega-embed-container--LYZqguO.js} +2 -2
  118. package/dist/{vega-loader.browser-DNST6uVf.js → vega-loader.browser-B9Pz0EGP.js} +298 -507
  119. package/dist/{vennDiagram-4TSXK5OY-BSZD8JcZ.js → vennDiagram-4TSXK5OY-BaYMoIpj.js} +1 -1
  120. package/dist/{wardleyDiagram-VM6X3IG4-48cUWkfA.js → wardleyDiagram-VM6X3IG4-DjHeuj8p.js} +1 -1
  121. package/dist/{xychartDiagram-S5SC5T6Z-CswYgYeu.js → xychartDiagram-S5SC5T6Z-Bc_hcuXS.js} +2 -2
  122. package/package.json +16 -16
  123. package/src/__mocks__/document.tsx +54 -0
  124. package/src/__mocks__/notebook.ts +2 -1
  125. package/src/__mocks__/requests.ts +0 -3
  126. package/src/__tests__/module-entry.test.ts +44 -0
  127. package/src/__tests__/mount.test.ts +1 -21
  128. package/src/__tests__/{host-imports.test.ts → replica-imports.test.ts} +12 -11
  129. package/src/components/ai/__tests__/ai-model-dropdown.test.tsx +63 -0
  130. package/src/components/ai/ai-model-dropdown.tsx +11 -6
  131. package/src/components/app-config/ai-config.tsx +7 -2
  132. package/src/components/app-config/user-config-form.tsx +35 -0
  133. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  134. package/src/components/chat/__tests__/notebook-access-selector.test.tsx +64 -0
  135. package/src/components/chat/acp/__tests__/session-tabs.test.tsx +43 -0
  136. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  137. package/src/components/chat/acp/blocks.tsx +3 -2
  138. package/src/components/chat/acp/session-tabs.tsx +8 -3
  139. package/src/components/chat/chat-components.tsx +2 -0
  140. package/src/components/chat/chat-panel.tsx +20 -101
  141. package/src/components/chat/chat-utils.ts +4 -8
  142. package/src/components/chat/notebook-access-selector.stories.tsx +22 -0
  143. package/src/components/chat/notebook-access-selector.tsx +100 -0
  144. package/src/components/data-table/TableBottomBar.tsx +4 -1
  145. package/src/components/data-table/TableTopBar.tsx +2 -0
  146. package/src/components/data-table/__tests__/chart-spec-model.test.ts +195 -195
  147. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  148. package/src/components/data-table/__tests__/columns.test.tsx +105 -0
  149. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  150. package/src/components/data-table/__tests__/data-table.test.tsx +1 -0
  151. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  152. package/src/components/data-table/__tests__/export-actions.test.tsx +105 -0
  153. package/src/components/data-table/__tests__/keyboard-selection.test.tsx +188 -0
  154. package/src/components/data-table/__tests__/pagination.test.tsx +28 -1
  155. package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
  156. package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
  157. package/src/components/data-table/cell-selection/feature.ts +2 -2
  158. package/src/components/data-table/cell-selection/types.ts +2 -2
  159. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
  160. package/src/components/data-table/charts/__tests__/spec-snapshot.test.ts +20 -20
  161. package/src/components/data-table/charts/__tests__/spec.test.ts +78 -52
  162. package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
  163. package/src/components/data-table/charts/chart-spec/spec.ts +68 -43
  164. package/src/components/data-table/charts/charts.tsx +40 -30
  165. package/src/components/data-table/charts/storage.ts +16 -5
  166. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  167. package/src/components/data-table/column-summary/chart-spec-model.tsx +23 -16
  168. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  169. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  170. package/src/components/data-table/columns.tsx +45 -46
  171. package/src/components/data-table/context-menu.tsx +36 -20
  172. package/src/components/data-table/data-table.tsx +17 -1
  173. package/src/components/data-table/date-popover.tsx +6 -4
  174. package/src/components/data-table/export-actions.tsx +76 -48
  175. package/src/components/data-table/pagination.tsx +44 -33
  176. package/src/components/data-table/range-focus/atoms.ts +4 -6
  177. package/src/components/data-table/range-focus/use-cell-range-selection.ts +65 -38
  178. package/src/components/data-table/range-focus/use-scroll-into-view.ts +21 -2
  179. package/src/components/data-table/renderers.tsx +16 -7
  180. package/src/components/data-table/row-viewer-panel/__tests__/row-viewer.test.tsx +24 -1
  181. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +4 -4
  182. package/src/components/data-table/search-highlight.tsx +79 -0
  183. package/src/components/data-table/types.ts +1 -0
  184. package/src/components/data-table/url-detector.tsx +49 -9
  185. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  186. package/src/components/datasources/__tests__/column-preview.test.tsx +96 -5
  187. package/src/components/datasources/__tests__/datasources-interactions.test.tsx +181 -0
  188. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  189. package/src/components/datasources/__tests__/utils.test.ts +57 -6
  190. package/src/components/datasources/column-preview.tsx +17 -12
  191. package/src/components/datasources/datasources.tsx +55 -42
  192. package/src/components/datasources/utils.ts +32 -23
  193. package/src/components/debugger/debugger-code.tsx +5 -0
  194. package/src/components/dependency-graph/dependency-graph-tree.tsx +8 -8
  195. package/src/components/dependency-graph/elements.ts +24 -21
  196. package/src/components/dependency-graph/minimap-content.tsx +2 -6
  197. package/src/components/dependency-graph/panels.tsx +4 -2
  198. package/src/components/dependency-graph/utils/__tests__/layout.test.ts +18 -11
  199. package/src/components/editor/SortableCell.tsx +1 -3
  200. package/src/components/editor/__tests__/Output.test.tsx +50 -32
  201. package/src/{core/attachments/__tests__/toast.test.tsx → components/editor/__tests__/agent-toast.test.tsx} +1 -1
  202. package/src/components/editor/__tests__/data-attributes.test.tsx +28 -13
  203. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  204. package/src/components/editor/__tests__/output-persistence.test.tsx +6 -1
  205. package/src/components/editor/__tests__/package-alert.test.tsx +22 -1
  206. package/src/components/editor/actions/__tests__/name-cell-input.test.tsx +69 -0
  207. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +34 -8
  208. package/src/components/editor/actions/__tests__/pair-with-agent.test.tsx +2 -6
  209. package/src/components/editor/actions/name-cell-input.tsx +4 -3
  210. package/src/components/editor/actions/pair-with-agent-commands.ts +2 -2
  211. package/src/components/editor/actions/pair-with-agent-modal.tsx +19 -16
  212. package/src/components/editor/actions/useCellActionButton.tsx +20 -26
  213. package/src/components/editor/actions/useNotebookActions.tsx +14 -19
  214. package/src/components/editor/actions/useRestartKernel.tsx +1 -1
  215. package/src/components/editor/actions/useSetCodeVisibility.ts +10 -11
  216. package/src/components/editor/actions/useSetOutputsExpanded.ts +13 -18
  217. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  218. package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
  219. package/src/components/editor/alerts/__tests__/connection-notice.test.tsx +1 -1
  220. package/src/components/editor/alerts/connection-notice.tsx +1 -1
  221. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  222. package/src/components/editor/app-container.tsx +1 -1
  223. package/src/components/editor/boundary/__tests__/ErrorBoundary.test.tsx +1 -1
  224. package/src/components/editor/cell/CreateCellButton.tsx +15 -21
  225. package/src/components/editor/cell/DeleteButton.tsx +3 -2
  226. package/src/components/editor/cell/RunButton.tsx +2 -2
  227. package/src/components/editor/cell/StagedAICell.tsx +10 -2
  228. package/src/components/editor/cell/StopButton.tsx +2 -2
  229. package/src/components/editor/cell/__tests__/StagedAICell.test.tsx +3 -1
  230. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  231. package/src/components/editor/cell/cell-actions.tsx +12 -1
  232. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  233. package/src/components/editor/cell/code/cell-editor.tsx +22 -36
  234. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  235. package/src/components/editor/cell/collapse.tsx +5 -3
  236. package/src/components/editor/cell/useAddCell.ts +4 -8
  237. package/src/components/editor/cell/useDeleteCell.tsx +10 -36
  238. package/src/components/editor/cell/useRunCells.ts +2 -5
  239. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  240. package/src/components/editor/cell/useSplitCell.tsx +2 -1
  241. package/src/components/editor/chrome/components/__tests__/feedback-button.test.tsx +1 -1
  242. package/src/components/editor/chrome/components/feedback-button.tsx +1 -1
  243. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +31 -2
  244. package/src/components/editor/chrome/panels/__tests__/runtime-details.test.tsx +1 -1
  245. package/src/components/editor/chrome/panels/__tests__/sandbox-recovery.test.tsx +1 -1
  246. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  247. package/src/components/editor/chrome/panels/logs-panel.tsx +2 -1
  248. package/src/components/editor/chrome/panels/outline/__tests__/outline-keyboard.test.tsx +84 -0
  249. package/src/components/editor/chrome/panels/outline/floating-outline.tsx +39 -14
  250. package/src/components/editor/chrome/panels/outline/useActiveOutline.tsx +3 -2
  251. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  252. package/src/components/editor/chrome/panels/secrets-panel.tsx +1 -0
  253. package/src/components/editor/chrome/panels/snippets-panel.tsx +10 -12
  254. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +9 -18
  255. package/src/components/editor/chrome/wrapper/__tests__/document-sync-status.test.tsx +92 -0
  256. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +61 -2
  257. package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +4 -4
  258. package/src/components/editor/chrome/wrapper/footer-items/backend-status.tsx +1 -1
  259. package/src/components/editor/chrome/wrapper/footer-items/document-sync-status.tsx +54 -0
  260. package/src/components/editor/chrome/wrapper/footer-items/lsp-status.tsx +1 -1
  261. package/src/components/editor/chrome/wrapper/footer-items/machine-stats.tsx +1 -1
  262. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  263. package/src/components/editor/chrome/wrapper/footer.tsx +2 -0
  264. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
  265. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  266. package/src/components/editor/columns/sortable-column.tsx +1 -1
  267. package/src/components/editor/connections/components.tsx +5 -10
  268. package/src/components/editor/controls/Controls.tsx +3 -3
  269. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  270. package/src/components/editor/controls/__tests__/keyboard-shortcuts.test.tsx +61 -0
  271. package/src/components/editor/controls/__tests__/notebook-menu-dropdown.test.tsx +10 -4
  272. package/src/components/editor/controls/command-palette.tsx +22 -10
  273. package/src/components/editor/controls/keyboard-shortcuts.tsx +14 -5
  274. package/src/components/editor/documentation.css +34 -11
  275. package/src/components/editor/dynamic-favicon.tsx +21 -40
  276. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +9 -23
  277. package/src/components/editor/errors/auto-fix.tsx +4 -8
  278. package/src/components/editor/errors/send-error-report-button.tsx +3 -6
  279. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  280. package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +47 -0
  281. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  282. package/src/components/editor/file-tree/file-explorer.tsx +48 -15
  283. package/src/components/editor/file-tree/file-name-input.tsx +1 -0
  284. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  285. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  286. package/src/components/editor/header/__tests__/status.test.tsx +5 -3
  287. package/src/components/editor/header/app-header.tsx +1 -1
  288. package/src/components/editor/header/status.tsx +40 -20
  289. package/src/components/editor/links/__tests__/cell-link.test.tsx +148 -0
  290. package/src/components/editor/links/cell-link.tsx +16 -6
  291. package/src/components/editor/navigation/__tests__/clipboard.test.ts +60 -38
  292. package/src/components/editor/navigation/__tests__/navigation.test.ts +73 -64
  293. package/src/components/editor/navigation/clipboard.ts +12 -11
  294. package/src/components/editor/navigation/focus-utils.ts +4 -4
  295. package/src/components/editor/navigation/multi-cell-action-toolbar.tsx +11 -20
  296. package/src/components/editor/navigation/navigation.ts +20 -28
  297. package/src/components/editor/notebook-banner.tsx +1 -0
  298. package/src/components/editor/notebook-cell.tsx +19 -13
  299. package/src/components/editor/output/MarimoErrorOutput.tsx +2 -1
  300. package/src/components/editor/output/MarimoTracebackOutput.tsx +19 -17
  301. package/src/components/editor/output/Outputs.css +8 -0
  302. package/src/components/editor/output/__tests__/traceback.test.tsx +27 -1
  303. package/src/components/editor/output/ansi-reduce.ts +33 -20
  304. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  305. package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +125 -69
  306. package/src/components/editor/package-alert.tsx +14 -7
  307. package/src/components/editor/renderers/cell-array.tsx +10 -17
  308. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +12 -6
  309. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  310. package/src/components/home/components.tsx +17 -31
  311. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  312. package/src/components/icons/large-spinner.tsx +3 -14
  313. package/src/components/icons/spinner.tsx +1 -1
  314. package/src/components/markdown/markdown-renderer.tsx +4 -8
  315. package/src/components/mcp/mcp-status-indicator.tsx +1 -1
  316. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  317. package/src/components/pages/edit-page.tsx +32 -17
  318. package/src/components/pages/home-page.tsx +164 -91
  319. package/src/components/scratchpad/scratchpad.test.tsx +97 -0
  320. package/src/components/scratchpad/scratchpad.tsx +20 -10
  321. package/src/components/slides/__tests__/minimap-actions.test.tsx +9 -25
  322. package/src/components/slides/__tests__/slide-scroll-hint.test.tsx +37 -0
  323. package/src/components/slides/minimap.tsx +7 -3
  324. package/src/components/slides/reveal-component.tsx +57 -25
  325. package/src/components/slides/reveal-slides.css +5 -4
  326. package/src/components/slides/slide-cell-view.tsx +4 -4
  327. package/src/components/slides/slide-form.tsx +1 -0
  328. package/src/components/slides/slide-scroll-hint.tsx +3 -1
  329. package/src/components/sort/SortableCellsProvider.tsx +2 -1
  330. package/src/components/tracing/tracing-keyboard.test.tsx +85 -0
  331. package/src/components/tracing/tracing.tsx +45 -41
  332. package/src/components/ui/__tests__/draggable-popover.test.tsx +24 -0
  333. package/src/components/ui/__tests__/range-slider.test.tsx +165 -0
  334. package/src/components/ui/alert-dialog.tsx +1 -1
  335. package/src/components/ui/draggable-popover.tsx +24 -2
  336. package/src/components/ui/range-slider.tsx +22 -9
  337. package/src/core/ai/__tests__/staged-cells.test.ts +26 -25
  338. package/src/core/ai/context/providers/datasource.ts +6 -2
  339. package/src/core/ai/staged-cells.ts +15 -18
  340. package/src/core/ai/tools/__tests__/edit-notebook-tool.test.ts +6 -22
  341. package/src/core/ai/tools/__tests__/registry.test.ts +17 -0
  342. package/src/core/ai/tools/base.ts +18 -15
  343. package/src/core/ai/tools/edit-notebook-tool.ts +75 -71
  344. package/src/core/ai/tools/registry.ts +13 -10
  345. package/src/core/ai/tools/sample-tool.ts +6 -7
  346. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -0
  347. package/src/core/cells/__tests__/actions.test.ts +474 -34
  348. package/src/core/cells/__tests__/add-missing-import.test.ts +7 -8
  349. package/src/core/cells/__tests__/cell.test.ts +39 -0
  350. package/src/core/cells/__tests__/cells.test.ts +260 -1165
  351. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
  352. package/src/core/cells/__tests__/document.test.ts +414 -0
  353. package/src/core/cells/__tests__/flush-document.test.ts +40 -0
  354. package/src/core/cells/__tests__/notebook-outline.test.ts +114 -0
  355. package/src/core/cells/__tests__/outputs.test.tsx +71 -53
  356. package/src/core/cells/__tests__/session.test.ts +117 -36
  357. package/src/core/cells/__tests__/with-document-save.test.ts +67 -0
  358. package/src/core/cells/actions.ts +391 -3
  359. package/src/core/cells/add-missing-import.ts +46 -42
  360. package/src/core/cells/cell.ts +14 -3
  361. package/src/core/cells/cells.ts +99 -599
  362. package/src/core/cells/columns.ts +38 -0
  363. package/src/core/cells/document.ts +392 -0
  364. package/src/core/cells/outputs.ts +12 -28
  365. package/src/core/cells/pending-delete-service.ts +2 -6
  366. package/src/core/cells/session.ts +1 -1
  367. package/src/core/cells/types.ts +22 -1
  368. package/src/core/codemirror/__tests__/setup.test.ts +29 -29
  369. package/src/core/codemirror/ai/__tests__/resources.test.ts +117 -0
  370. package/src/core/codemirror/ai/resources.ts +1 -1
  371. package/src/core/codemirror/cells/__tests__/extensions.test.ts +42 -1
  372. package/src/core/codemirror/cells/extensions.ts +27 -9
  373. package/src/core/codemirror/cells/state.ts +1 -1
  374. package/src/core/codemirror/format.ts +6 -5
  375. package/src/core/codemirror/keymaps/vim.ts +4 -8
  376. package/src/core/codemirror/language/__tests__/sql.test.ts +22 -20
  377. package/src/core/codemirror/language/__tests__/utils.test.ts +187 -15
  378. package/src/core/codemirror/language/cell-code.ts +114 -0
  379. package/src/core/codemirror/language/extension.ts +52 -35
  380. package/src/core/codemirror/language/languages/markdown.ts +8 -6
  381. package/src/core/codemirror/language/languages/python.ts +16 -10
  382. package/src/core/codemirror/language/languages/sql/sql.ts +16 -24
  383. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  384. package/src/core/codemirror/language/types.ts +7 -7
  385. package/src/core/codemirror/language/utils.ts +9 -39
  386. package/src/core/codemirror/lsp/notebook-lsp.ts +6 -5
  387. package/src/core/codemirror/readonly/__tests__/extension.test.ts +1 -1
  388. package/src/core/codemirror/readonly/extension.ts +2 -2
  389. package/src/core/codemirror/rtc/extension.ts +24 -13
  390. package/src/core/codemirror/rtc/loro/awareness.ts +34 -19
  391. package/src/core/codemirror/rtc/loro/sync.ts +1 -0
  392. package/src/core/codemirror/utils.ts +1 -0
  393. package/src/core/config/__tests__/config-schema.test.ts +12 -5
  394. package/src/core/config/config-schema.ts +7 -1
  395. package/src/core/datasets/__tests__/data-source-discovery.test.ts +5 -5
  396. package/src/core/datasets/data-source-discovery.ts +4 -6
  397. package/src/core/edit-app.tsx +19 -23
  398. package/src/core/errors/error-entries.ts +3 -0
  399. package/src/core/export/hooks.ts +1 -1
  400. package/src/core/export/layout.ts +2 -2
  401. package/src/core/hotkeys/actions.ts +2 -4
  402. package/src/core/islands/__tests__/bootstrap.test.ts +73 -43
  403. package/src/core/islands/__tests__/bridge.test.ts +72 -25
  404. package/src/core/islands/bootstrap.ts +46 -53
  405. package/src/core/islands/bridge.ts +74 -23
  406. package/src/core/islands/components/__tests__/IslandControls.test.tsx +7 -5
  407. package/src/core/islands/worker/__tests__/controller.test.ts +62 -4
  408. package/src/core/islands/worker/controller.ts +4 -10
  409. package/src/core/islands/worker/worker.tsx +33 -33
  410. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  411. package/src/core/kernel/__tests__/handlers.test.ts +7 -153
  412. package/src/core/kernel/handlers.ts +18 -59
  413. package/src/core/network/__tests__/connection.test.ts +1 -1
  414. package/src/core/network/__tests__/requests-lazy.test.ts +1 -3
  415. package/src/core/network/connection-notice.ts +1 -1
  416. package/src/core/network/connection.ts +1 -1
  417. package/src/core/network/requests-lazy.ts +1 -4
  418. package/src/core/network/requests-network.ts +0 -25
  419. package/src/core/network/requests-static.ts +0 -3
  420. package/src/core/network/requests-toasting.tsx +0 -3
  421. package/src/core/network/types.ts +0 -12
  422. package/src/core/network/useConnectionNotice.ts +8 -2
  423. package/src/core/notebook/attachments.ts +38 -0
  424. package/src/core/notebook/document/__tests__/codemirror.test.ts +552 -0
  425. package/src/core/notebook/document/__tests__/draft.test.ts +363 -0
  426. package/src/core/notebook/document/__tests__/fixtures.ts +92 -0
  427. package/src/core/notebook/document/__tests__/hold.test.ts +88 -0
  428. package/src/core/notebook/document/__tests__/hooks.test.tsx +391 -0
  429. package/src/core/notebook/document/__tests__/lifecycle-scenarios.test.ts +61 -0
  430. package/src/core/notebook/document/__tests__/model.test.ts +247 -0
  431. package/src/core/notebook/document/__tests__/protocol-fixtures.ts +38 -0
  432. package/src/core/notebook/document/__tests__/recovery-scenarios.test.ts +230 -0
  433. package/src/core/notebook/document/__tests__/scenario-fixture.ts +43 -0
  434. package/src/core/notebook/document/__tests__/scenarios.test.ts +205 -0
  435. package/src/core/notebook/document/__tests__/state.test.ts +459 -0
  436. package/src/core/notebook/document/__tests__/submission.test.ts +203 -0
  437. package/src/core/notebook/document/__tests__/test-host.test.ts +92 -0
  438. package/src/core/notebook/document/__tests__/test-host.ts +247 -0
  439. package/src/core/notebook/document/__tests__/test-notebook.ts +149 -0
  440. package/src/core/notebook/document/__tests__/user.ts +61 -0
  441. package/src/core/notebook/document/codemirror.ts +216 -0
  442. package/src/core/notebook/document/draft.ts +502 -0
  443. package/src/core/notebook/document/hooks.tsx +118 -0
  444. package/src/core/notebook/document/index.ts +24 -0
  445. package/src/core/notebook/document/model.ts +105 -0
  446. package/src/core/notebook/document/select.ts +41 -0
  447. package/src/core/notebook/document/state.ts +378 -0
  448. package/src/core/notebook/document/sync.ts +418 -0
  449. package/src/core/notebook/index.ts +18 -0
  450. package/src/core/notebook/replica/AGENTS.md +11 -0
  451. package/src/core/notebook/replica/__tests__/attachments.test.ts +56 -0
  452. package/src/core/notebook/replica/__tests__/document.test.ts +126 -0
  453. package/src/core/notebook/replica/__tests__/fixtures.ts +79 -0
  454. package/src/core/notebook/replica/__tests__/replica.test.ts +550 -0
  455. package/src/core/notebook/replica/backend.ts +19 -0
  456. package/src/core/notebook/replica/connection.ts +42 -0
  457. package/src/core/notebook/replica/index.ts +13 -0
  458. package/src/core/notebook/replica/replica.ts +286 -0
  459. package/src/core/notebook/replica/resources/attachments.ts +22 -0
  460. package/src/core/notebook/replica/resources/document.ts +101 -0
  461. package/src/core/packages/__tests__/useSandboxController.test.tsx +1 -1
  462. package/src/core/packages/useSandboxController.ts +1 -1
  463. package/src/core/run-app.tsx +48 -51
  464. package/src/core/runtime/__tests__/adapter.test.ts +1 -1
  465. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  466. package/src/core/runtime/__tests__/runtime.test.ts +32 -58
  467. package/src/core/runtime/adapter.ts +1 -1
  468. package/src/core/runtime/config.ts +2 -2
  469. package/src/core/runtime/runtime.ts +38 -42
  470. package/src/core/runtime/types.ts +0 -2
  471. package/src/core/saving/filename.ts +1 -1
  472. package/src/core/saving/save-component.tsx +10 -4
  473. package/src/core/saving/useAutoSave.ts +1 -1
  474. package/src/core/session/__tests__/backend.test.ts +235 -0
  475. package/src/core/session/__tests__/useNotebookReplica.hook.test.tsx +183 -0
  476. package/src/core/session/__tests__/useNotebookSession.hook.test.tsx +663 -0
  477. package/src/core/session/active-notebook.ts +11 -0
  478. package/src/core/session/backend.ts +125 -0
  479. package/src/core/{websocket → session}/types.ts +2 -2
  480. package/src/core/session/useNotebookReplica.ts +72 -0
  481. package/src/core/{websocket → session}/useNotebookSession.tsx +74 -68
  482. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  483. package/src/core/state/jotai.ts +1 -1
  484. package/src/core/static/__tests__/files.test.ts +38 -10
  485. package/src/core/static/files.ts +2 -0
  486. package/src/core/{websocket → transports}/__tests__/connection-state.test.ts +1 -13
  487. package/src/core/transports/__tests__/server.test.ts +408 -0
  488. package/src/core/transports/__tests__/static.test.ts +175 -0
  489. package/src/core/transports/__tests__/wasm.test.ts +166 -0
  490. package/src/core/{websocket → transports}/connection-state.ts +9 -16
  491. package/src/core/transports/server.ts +152 -0
  492. package/src/core/transports/static.ts +84 -0
  493. package/src/core/transports/wasm.ts +97 -0
  494. package/src/core/wasm/__tests__/bridge.test.ts +213 -50
  495. package/src/core/wasm/__tests__/worker.test.ts +187 -0
  496. package/src/core/wasm/bridge.ts +75 -69
  497. package/src/core/wasm/rpc.ts +15 -3
  498. package/src/core/wasm/store.ts +3 -3
  499. package/src/core/wasm/worker/bootstrap.ts +55 -51
  500. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  501. package/src/core/wasm/worker/save-worker.ts +9 -10
  502. package/src/core/wasm/worker/types.ts +29 -18
  503. package/src/core/wasm/worker/worker.ts +88 -44
  504. package/src/css/globals.css +66 -22
  505. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  506. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  507. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  508. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  509. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  510. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  511. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  512. package/src/hooks/useAsyncData.ts +3 -3
  513. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  514. package/src/hooks/useDebounce.ts +6 -10
  515. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  516. package/src/hooks/useDelayElapsed.ts +14 -10
  517. package/src/hooks/useElapsedTime.ts +3 -3
  518. package/src/hooks/useEventListener.ts +1 -2
  519. package/src/hooks/useInputHistory.ts +4 -7
  520. package/src/hooks/useInternalStateWithSync.ts +6 -6
  521. package/src/hooks/useResizeObserver.ts +4 -7
  522. package/src/hooks/useScript.ts +67 -70
  523. package/src/hooks/useStateForKey.ts +93 -0
  524. package/src/mount.tsx +1 -5
  525. package/src/plugins/__tests__/plugin-object-schemas.test.ts +104 -0
  526. package/src/plugins/core/registerReactComponent.tsx +1 -1
  527. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  528. package/src/plugins/impl/DataEditorPlugin.tsx +1 -1
  529. package/src/plugins/impl/DataTablePlugin.tsx +69 -37
  530. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  531. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  532. package/src/plugins/impl/FormPlugin.tsx +1 -1
  533. package/src/plugins/impl/RangeSliderPlugin.tsx +16 -2
  534. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  535. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  536. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +387 -1
  537. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  538. package/src/plugins/impl/__tests__/RangeSliderPlugin.test.tsx +109 -0
  539. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  540. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +153 -0
  541. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  542. package/src/plugins/impl/anywidget/model.ts +3 -1
  543. package/src/plugins/impl/anywidget/registry.ts +18 -14
  544. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  545. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  546. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  547. package/src/plugins/impl/chat/chat-ui.tsx +14 -11
  548. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  549. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  550. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  551. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  552. package/src/plugins/impl/data-explorer/components/column-summary.tsx +4 -2
  553. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  554. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -2
  555. package/src/plugins/impl/data-frames/__tests__/sidebar.test.tsx +160 -0
  556. package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +66 -66
  557. package/src/plugins/impl/data-frames/panel.tsx +43 -16
  558. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  559. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  560. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  561. package/src/plugins/impl/panel/utils.ts +17 -0
  562. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +2 -5
  563. package/src/plugins/impl/plotly/selection.ts +25 -19
  564. package/src/plugins/impl/vega/VegaPlugin.tsx +2 -3
  565. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  566. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  567. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  568. package/src/stories/button.stories.tsx +1 -0
  569. package/src/stories/cell.stories.tsx +1 -1
  570. package/src/stories/data-table.stories.tsx +31 -0
  571. package/src/stories/editor.stories.tsx +24 -17
  572. package/src/stories/form-wrapper.stories.tsx +2 -2
  573. package/src/stories/layout/vertical/one-column.stories.tsx +1 -1
  574. package/src/stories/runtime-story.tsx +23 -16
  575. package/src/stories/select.stories.tsx +2 -2
  576. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  577. package/src/utils/__tests__/arrays.test.ts +35 -0
  578. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  579. package/src/utils/__tests__/{createReducer.test.ts → createReducer.test.tsx} +29 -0
  580. package/src/utils/__tests__/numbers.test.ts +105 -1
  581. package/src/utils/__tests__/objects.test.ts +21 -0
  582. package/src/utils/arrays.ts +11 -2
  583. package/src/utils/blob.ts +1 -1
  584. package/src/utils/createReducer.ts +53 -26
  585. package/src/utils/maps.ts +0 -3
  586. package/src/utils/numbers.ts +79 -18
  587. package/src/utils/objects.ts +4 -9
  588. package/src/utils/once.ts +19 -19
  589. package/src/utils/tracer.ts +9 -5
  590. package/src/utils/zod-utils.ts +1 -3
  591. package/dist/any-language-editor-DZ_YTVus.js +0 -76
  592. package/dist/assets/__vite-browser-external-BXYkiFsk.js +0 -1
  593. package/dist/assets/worker-HPY9fbhz.js +0 -114
  594. package/dist/mermaid-HWGCJPDP-ByCsZnYF.js +0 -2
  595. package/dist/precisionRound-DMIVQrt2.js +0 -97
  596. package/src/core/ai/tools/__tests__/run-cells-tool.test.ts +0 -656
  597. package/src/core/ai/tools/run-cells-tool.ts +0 -303
  598. package/src/core/attachments/state.ts +0 -21
  599. package/src/core/cells/__tests__/apply-transaction.test.ts +0 -991
  600. package/src/core/cells/__tests__/document-changes.test.ts +0 -1140
  601. package/src/core/cells/__tests__/document-roundtrip.test.ts +0 -392
  602. package/src/core/cells/document-changes.ts +0 -944
  603. package/src/core/host/AGENTS.md +0 -6
  604. package/src/core/host/__tests__/events.test.ts +0 -46
  605. package/src/core/host/__tests__/notebook-client.test.ts +0 -191
  606. package/src/core/host/__tests__/protocol.test.ts +0 -214
  607. package/src/core/host/connection.ts +0 -81
  608. package/src/core/host/events.ts +0 -60
  609. package/src/core/host/notebook-client.ts +0 -226
  610. package/src/core/host/protocol.ts +0 -1943
  611. package/src/core/state/__tests__/jotai.test.ts +0 -25
  612. package/src/core/wasm/worker/message-buffer.ts +0 -33
  613. package/src/core/websocket/__tests__/createConnectionTransport.test.ts +0 -49
  614. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +0 -140
  615. package/src/core/websocket/__tests__/useNotebookSession.hook.test.tsx +0 -883
  616. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  617. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -335
  618. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  619. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  620. package/src/core/websocket/transports/basic.ts +0 -102
  621. package/src/core/websocket/transports/create.ts +0 -31
  622. package/src/core/websocket/transports/sse.ts +0 -308
  623. package/src/core/websocket/transports/transport.ts +0 -50
  624. package/src/core/websocket/transports/ws.ts +0 -98
  625. package/src/core/websocket/useNotebookClient.ts +0 -80
  626. /package/src/{core/attachments/toast.ts → components/editor/agent-toast.ts} +0 -0
  627. /package/src/core/{websocket → session}/connection-utils.ts +0 -0
@@ -4,10 +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
- afterEach,
10
10
  beforeAll,
11
+ afterEach,
11
12
  beforeEach,
12
13
  describe,
13
14
  expect,
@@ -17,11 +18,12 @@ import {
17
18
  import { MockNotebook } from "@/__mocks__/notebook";
18
19
  import { cellId } from "@/__tests__/branded";
19
20
  import type { CellHandle } from "@/components/editor/notebook-cell";
20
- import { CellId, SCRATCH_CELL_ID, SETUP_CELL_ID } from "@/core/cells/ids";
21
+ import { CellId, SCRATCH_CELL_ID } from "@/core/cells/ids";
21
22
  import { foldAllBulk, unfoldAllBulk } from "@/core/codemirror/editing/commands";
22
23
  import { adaptiveLanguageConfiguration } from "@/core/codemirror/language/extension";
23
24
  import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
24
25
  import type { OutputMessage } from "@/core/kernel/messages";
26
+ import type { CellConfig } from "@/core/network/types";
25
27
  import {
26
28
  type CollapsibleTree,
27
29
  MultiColumn,
@@ -34,13 +36,17 @@ import {
34
36
  type NotebookState,
35
37
  notebookAtom,
36
38
  } from "../cells";
37
- import { exportedForTesting as documentTransactionTestExports } from "../document-changes";
38
39
  import {
39
40
  focusAndScrollCellIntoView,
40
41
  scrollToBottom,
41
42
  scrollToTop,
42
43
  } from "../scrollCellIntoView";
43
- import type { CellData } from "../types";
44
+ import {
45
+ type CellData,
46
+ createCell,
47
+ createCellConfig,
48
+ createCellRuntimeState,
49
+ } from "../types";
44
50
 
45
51
  vi.mock("@/core/codemirror/editing/commands", () => ({
46
52
  foldAllBulk: vi.fn(),
@@ -121,8 +127,7 @@ describe("cell reducer", () => {
121
127
  let cells: ReturnType<typeof flattenTopLevelNotebookCells>;
122
128
  let firstCellId: CellId;
123
129
 
124
- const actions = createActions((action) => {
125
- state = reducer(state, action);
130
+ const mountEditors = () => {
126
131
  for (const [cellIdString, handle] of Object.entries(state.cellHandles)) {
127
132
  // @ts-expect-error - Typescript Object.entries doesn't know that keys are CellId
128
133
  const cid: CellId = cellIdString;
@@ -136,7 +141,76 @@ describe("cell reducer", () => {
136
141
  }
137
142
  }
138
143
  cells = flattenTopLevelNotebookCells(state);
139
- });
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
+ }
140
214
 
141
215
  let i = 0;
142
216
  const originalCreate = CellId.create.bind(CellId);
@@ -152,193 +226,14 @@ describe("cell reducer", () => {
152
226
 
153
227
  state = initialNotebookState();
154
228
  state.cellIds = MultiColumn.from([]);
155
- actions.createNewCell({ cellId: "__end__", before: false });
229
+ seedCell({ cellId: "__end__", before: false });
156
230
  firstCellId = state.cellIds.inOrderIds[0];
157
231
  });
158
232
 
159
- afterEach(() => {
160
- documentTransactionTestExports.cancelPendingChanges();
161
- });
162
-
163
233
  afterAll(() => {
164
234
  CellId.create = originalCreate;
165
235
  });
166
236
 
167
- it("can add a cell after another cell", () => {
168
- actions.createNewCell({
169
- cellId: firstCellId,
170
- before: false,
171
- });
172
- expect(formatCells(state)).toMatchInlineSnapshot(`
173
- "
174
- [0] ''
175
-
176
- [1] ''
177
- "
178
- `);
179
- });
180
-
181
- it("can add a cell to the end with code", () => {
182
- actions.createNewCell({
183
- cellId: "__end__",
184
- code: "import numpy as np",
185
- before: false,
186
- });
187
- expect(formatCells(state)).toMatchInlineSnapshot(`
188
- "
189
- [0] ''
190
-
191
- [1] 'import numpy as np'
192
- "
193
- `);
194
-
195
- // Cell should be added to the end and edited
196
- expect(cells[1].code).toBe("import numpy as np");
197
- expect(cells[1].edited).toBe(true);
198
- expect(cells[1].lastCodeRun).toBe(null);
199
- });
200
-
201
- it("can add a cell before another cell", () => {
202
- actions.createNewCell({
203
- cellId: firstCellId,
204
- before: true,
205
- });
206
- expect(formatCells(state)).toMatchInlineSnapshot(`
207
- "
208
- [1] ''
209
-
210
- [0] ''
211
- "
212
- `);
213
- });
214
-
215
- it("can add a cell with name and config", () => {
216
- actions.createNewCell({
217
- cellId: firstCellId,
218
- before: false,
219
- code: "x = 1",
220
- name: "My Cell",
221
- config: { hide_code: true, disabled: false },
222
- });
223
- const newCellId = state.cellIds.inOrderIds[1];
224
- expect(state.cellData[newCellId].name).toBe("My Cell");
225
- expect(state.cellData[newCellId].config.hide_code).toBe(true);
226
- expect(state.cellData[newCellId].config.disabled).toBe(false);
227
- });
228
-
229
- it("can delete a Python cell and undo delete", () => {
230
- actions.createNewCell({
231
- cellId: firstCellId,
232
- before: false,
233
- });
234
- actions.deleteCell({
235
- cellId: firstCellId,
236
- });
237
- expect(formatCells(state)).toMatchInlineSnapshot(`
238
- "
239
- [1] ''
240
- "
241
- `);
242
-
243
- // undo
244
- actions.undoDeleteCell();
245
- expect(formatCells(state)).toMatchInlineSnapshot(`
246
- "
247
- [2] ''
248
-
249
- [1] ''
250
- "
251
- `);
252
-
253
- // Verify scrollKey is set to the restored cell
254
- expect(state.scrollKey).toBe("2");
255
- });
256
-
257
- it("can delete a SQL cell and undo delete", () => {
258
- actions.createNewCell({
259
- cellId: firstCellId,
260
- before: false,
261
- code: `df = mo.sql("""SELECT * FROM table""")`,
262
- });
263
- const newCellId = state.cellIds.getColumns()[0].atOrThrow(1);
264
- actions.deleteCell({
265
- cellId: newCellId,
266
- });
267
- expect(formatCells(state)).toMatchInlineSnapshot(`
268
- "
269
- [0] ''
270
- "
271
- `);
272
-
273
- // undo
274
- actions.undoDeleteCell();
275
- expect(formatCells(state)).toMatchInlineSnapshot(`
276
- "
277
- [0] ''
278
-
279
- [2] 'df = mo.sql("""SELECT * FROM table""")'
280
- "
281
- `);
282
- });
283
-
284
- it("can delete a Markdown cell and undo delete", () => {
285
- const text = "The quick brown fox jumps over the lazy dog.";
286
- actions.createNewCell({
287
- cellId: firstCellId,
288
- before: false,
289
- code: `mo.md(r"""${text}""")`,
290
- });
291
- const newCellId = state.cellIds.getColumns()[0].atOrThrow(1);
292
- actions.deleteCell({
293
- cellId: newCellId,
294
- });
295
- expect(formatCells(state)).toMatchInlineSnapshot(`
296
- "
297
- [0] ''
298
- "
299
- `);
300
-
301
- // undo
302
- actions.undoDeleteCell();
303
- expect(formatCells(state)).toMatchInlineSnapshot(`
304
- "
305
- [0] ''
306
-
307
- [2] 'mo.md(r"""The quick brown fox jumps over the lazy dog.""")'
308
- "
309
- `);
310
- });
311
-
312
- it("undo delete restores cell config (hide_code, disabled)", () => {
313
- actions.createNewCell({
314
- cellId: firstCellId,
315
- before: false,
316
- code: "x = 1",
317
- });
318
- const newCellId = state.cellIds.getColumns()[0].atOrThrow(1);
319
-
320
- // Set config to hidden and disabled
321
- actions.updateCellConfig({
322
- cellId: newCellId,
323
- config: { hide_code: true, disabled: true },
324
- });
325
- expect(state.cellData[newCellId].config.hide_code).toBe(true);
326
- expect(state.cellData[newCellId].config.disabled).toBe(true);
327
-
328
- // Delete the cell
329
- actions.deleteCell({ cellId: newCellId });
330
- expect(state.cellIds.inOrderIds).not.toContain(newCellId);
331
-
332
- // Undo delete
333
- actions.undoDeleteCell();
334
- const restoredCellId =
335
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
336
-
337
- // Config should be preserved
338
- expect(state.cellData[restoredCellId].config.hide_code).toBe(true);
339
- expect(state.cellData[restoredCellId].config.disabled).toBe(true);
340
- });
341
-
342
237
  it("can update a cell", () => {
343
238
  actions.updateCellCode({
344
239
  cellId: firstCellId,
@@ -353,7 +248,7 @@ describe("cell reducer", () => {
353
248
  });
354
249
 
355
250
  it("can move a cell", () => {
356
- actions.createNewCell({
251
+ seedCell({
357
252
  cellId: firstCellId,
358
253
  before: false,
359
254
  });
@@ -467,11 +362,11 @@ describe("cell reducer", () => {
467
362
  });
468
363
 
469
364
  it("can drag and drop a cell", () => {
470
- actions.createNewCell({
365
+ seedCell({
471
366
  cellId: firstCellId,
472
367
  before: false,
473
368
  });
474
- actions.createNewCell({
369
+ seedCell({
475
370
  cellId: cellId("1"),
476
371
  before: false,
477
372
  });
@@ -517,11 +412,11 @@ describe("cell reducer", () => {
517
412
  });
518
413
 
519
414
  it("can move a cell to an exact index within a column", () => {
520
- actions.createNewCell({
415
+ seedCell({
521
416
  cellId: firstCellId,
522
417
  before: false,
523
418
  });
524
- actions.createNewCell({
419
+ seedCell({
525
420
  cellId: cellId("1"),
526
421
  before: false,
527
422
  });
@@ -545,11 +440,11 @@ describe("cell reducer", () => {
545
440
  });
546
441
 
547
442
  it("can move a cell to an exact index across columns", () => {
548
- actions.createNewCell({
443
+ seedCell({
549
444
  cellId: firstCellId,
550
445
  before: false,
551
446
  });
552
- actions.createNewCell({
447
+ seedCell({
553
448
  cellId: cellId("1"),
554
449
  before: false,
555
450
  });
@@ -578,11 +473,11 @@ describe("cell reducer", () => {
578
473
  });
579
474
 
580
475
  it("moveCellToIndex is a no-op when moving to the same position", () => {
581
- actions.createNewCell({
476
+ seedCell({
582
477
  cellId: firstCellId,
583
478
  before: false,
584
479
  });
585
- actions.createNewCell({
480
+ seedCell({
586
481
  cellId: cellId("1"),
587
482
  before: false,
588
483
  });
@@ -600,7 +495,7 @@ describe("cell reducer", () => {
600
495
  });
601
496
 
602
497
  it("moveCellToIndex is a no-op for an invalid columnId", () => {
603
- actions.createNewCell({
498
+ seedCell({
604
499
  cellId: firstCellId,
605
500
  before: false,
606
501
  });
@@ -617,11 +512,11 @@ describe("cell reducer", () => {
617
512
  });
618
513
 
619
514
  it("can move multiple cells relative to target", () => {
620
- actions.createNewCell({
515
+ seedCell({
621
516
  cellId: firstCellId,
622
517
  before: false,
623
518
  });
624
- actions.createNewCell({
519
+ seedCell({
625
520
  cellId: cellId("1"),
626
521
  before: false,
627
522
  });
@@ -652,143 +547,6 @@ describe("cell reducer", () => {
652
547
  `);
653
548
  });
654
549
 
655
- it("can undo cut-paste (move with previousPlacements)", () => {
656
- actions.createNewCell({
657
- cellId: firstCellId,
658
- before: false,
659
- });
660
- actions.createNewCell({
661
- cellId: cellId("1"),
662
- before: false,
663
- });
664
- expect(formatCells(state)).toMatchInlineSnapshot(`
665
- "
666
- [0] ''
667
-
668
- [1] ''
669
-
670
- [2] ''
671
- "
672
- `);
673
-
674
- const col = state.cellIds.findWithId(firstCellId);
675
- const previousPlacements = [
676
- {
677
- columnId: col.id,
678
- index: col.indexOfOrThrow(
679
- firstCellId,
680
- ) as import("@/utils/id-tree").CellIndex,
681
- },
682
- {
683
- columnId: col.id,
684
- index: col.indexOfOrThrow(
685
- cellId("1"),
686
- ) as import("@/utils/id-tree").CellIndex,
687
- },
688
- ];
689
-
690
- actions.moveCellsRelativeTo({
691
- cellIds: [firstCellId, cellId("1")],
692
- targetCellId: cellId("2"),
693
- position: "after",
694
- previousPlacements,
695
- });
696
- expect(formatCells(state)).toMatchInlineSnapshot(`
697
- "
698
- [2] ''
699
-
700
- [0] ''
701
-
702
- [1] ''
703
- "
704
- `);
705
-
706
- actions.undoDeleteCell();
707
- expect(formatCells(state)).toMatchInlineSnapshot(`
708
- "
709
- [0] ''
710
-
711
- [1] ''
712
-
713
- [2] ''
714
- "
715
- `);
716
- });
717
-
718
- it("undo order: cut-paste then delete — first undo restores delete, second undo undoes move", () => {
719
- actions.createNewCell({
720
- cellId: firstCellId,
721
- before: false,
722
- });
723
- actions.createNewCell({
724
- cellId: cellId("1"),
725
- before: false,
726
- });
727
-
728
- const col = state.cellIds.findWithId(firstCellId);
729
- const previousPlacements = [
730
- {
731
- columnId: col.id,
732
- index: col.indexOfOrThrow(
733
- firstCellId,
734
- ) as import("@/utils/id-tree").CellIndex,
735
- },
736
- {
737
- columnId: col.id,
738
- index: col.indexOfOrThrow(
739
- cellId("1"),
740
- ) as import("@/utils/id-tree").CellIndex,
741
- },
742
- ];
743
-
744
- actions.moveCellsRelativeTo({
745
- cellIds: [firstCellId, cellId("1")],
746
- targetCellId: cellId("2"),
747
- position: "after",
748
- previousPlacements,
749
- });
750
- expect(formatCells(state)).toMatchInlineSnapshot(`
751
- "
752
- [2] ''
753
-
754
- [0] ''
755
-
756
- [1] ''
757
- "
758
- `);
759
-
760
- actions.deleteCell({ cellId: cellId("2") });
761
- expect(formatCells(state)).toMatchInlineSnapshot(`
762
- "
763
- [0] ''
764
-
765
- [1] ''
766
- "
767
- `);
768
-
769
- actions.undoDeleteCell();
770
- expect(formatCells(state)).toMatchInlineSnapshot(`
771
- "
772
- [3] ''
773
-
774
- [0] ''
775
-
776
- [1] ''
777
- "
778
- `);
779
-
780
- actions.undoDeleteCell();
781
- expect(formatCells(state)).toMatchInlineSnapshot(`
782
- "
783
- [0] ''
784
-
785
- [1] ''
786
-
787
- [3] ''
788
- "
789
- `);
790
- });
791
-
792
550
  it("keeps error evidence after an edit and a new queued run", () => {
793
551
  const code = "raise ValueError('old failure')";
794
552
  const output: OutputMessage = {
@@ -1172,7 +930,7 @@ describe("cell reducer", () => {
1172
930
  formattingChange: false,
1173
931
  });
1174
932
  // Add cell
1175
- actions.createNewCell({
933
+ seedCell({
1176
934
  cellId: firstCellId,
1177
935
  before: false,
1178
936
  });
@@ -1277,33 +1035,6 @@ describe("cell reducer", () => {
1277
1035
  expect(cell.edited).toBe(false);
1278
1036
  });
1279
1037
 
1280
- it("can update a cells config", () => {
1281
- const firstCellId = cells[0].id;
1282
- let cell = cells[0];
1283
- // Starts empty
1284
- expect(cell.config).toEqual({
1285
- disabled: false,
1286
- hide_code: false,
1287
- column: null,
1288
- expand_output: false,
1289
- });
1290
-
1291
- actions.updateCellConfig({
1292
- cellId: firstCellId,
1293
- config: { disabled: true },
1294
- });
1295
- cell = cells[0];
1296
- expect(cell.config.disabled).toBe(true);
1297
-
1298
- // Revert
1299
- actions.updateCellConfig({
1300
- cellId: firstCellId,
1301
- config: { disabled: false },
1302
- });
1303
- cell = cells[0];
1304
- expect(cell.config.disabled).toBe(false);
1305
- });
1306
-
1307
1038
  it("can run a stopped cell", () => {
1308
1039
  // Update code of first cell
1309
1040
  actions.updateCellCode({
@@ -1579,7 +1310,9 @@ describe("cell reducer", () => {
1579
1310
  response: secret,
1580
1311
  });
1581
1312
 
1582
- expect(cells[0].consoleOutputs).toEqual([{ ...prompt, response: "" }]);
1313
+ expect(cells[0].consoleOutputs).toEqual([
1314
+ { ...prompt, id: 1, response: "" },
1315
+ ]);
1583
1316
  expect(JSON.stringify(cells[0].consoleOutputs)).not.toContain(secret);
1584
1317
  });
1585
1318
 
@@ -1707,8 +1440,8 @@ describe("cell reducer", () => {
1707
1440
  });
1708
1441
 
1709
1442
  it("can send a cell to the top", () => {
1710
- actions.createNewCell({ cellId: firstCellId, before: false });
1711
- actions.createNewCell({ cellId: cellId("1"), before: false });
1443
+ seedCell({ cellId: firstCellId, before: false });
1444
+ seedCell({ cellId: cellId("1"), before: false });
1712
1445
  actions.sendToTop({ cellId: cellId("2") });
1713
1446
  expect(formatCells(state)).toMatchInlineSnapshot(`
1714
1447
  "
@@ -1722,8 +1455,8 @@ describe("cell reducer", () => {
1722
1455
  });
1723
1456
 
1724
1457
  it("can send a cell to the bottom", () => {
1725
- actions.createNewCell({ cellId: firstCellId, before: false });
1726
- actions.createNewCell({ cellId: cellId("1"), before: false });
1458
+ seedCell({ cellId: firstCellId, before: false });
1459
+ seedCell({ cellId: cellId("1"), before: false });
1727
1460
  actions.sendToBottom({ cellId: firstCellId });
1728
1461
  expect(formatCells(state)).toMatchInlineSnapshot(`
1729
1462
  "
@@ -1737,8 +1470,8 @@ describe("cell reducer", () => {
1737
1470
  });
1738
1471
 
1739
1472
  it("can focus cells", () => {
1740
- actions.createNewCell({ cellId: firstCellId, before: false });
1741
- actions.createNewCell({ cellId: cellId("1"), before: false });
1473
+ seedCell({ cellId: firstCellId, before: false });
1474
+ seedCell({ cellId: cellId("1"), before: false });
1742
1475
 
1743
1476
  actions.focusCell({ cellId: cellId("1"), where: "before" });
1744
1477
  expect(focusAndScrollCellIntoView).toHaveBeenCalledWith(
@@ -1754,199 +1487,13 @@ describe("cell reducer", () => {
1754
1487
  expect(scrollToBottom).toHaveBeenCalled();
1755
1488
  });
1756
1489
 
1757
- it("can update cell name", () => {
1758
- actions.updateCellName({ cellId: firstCellId, name: "Test Cell" });
1759
- expect(state.cellData[firstCellId].name).toBe("Test Cell");
1760
- });
1761
-
1762
- it("can set cell IDs and codes", () => {
1763
- const newIds = [cellId("3"), cellId("4"), cellId("5")];
1764
- const newCodes = ["code1", "code2", "code3"];
1765
-
1766
- actions.setCellIds({ cellIds: newIds });
1767
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).topLevelIds).toEqual(newIds);
1490
+ it("can fold and unfold all cells", () => {
1491
+ actions.foldAll();
1492
+ expect(foldAllBulk).toHaveBeenCalled();
1768
1493
 
1769
- // When codeIsStale is false, lastCodeRun should match code
1770
- actions.setCellCodes({
1771
- codes: newCodes,
1772
- ids: newIds,
1773
- codeIsStale: false,
1774
- });
1775
- newIds.forEach((id, index) => {
1776
- expect(state.cellData[id].code).toBe(newCodes[index]);
1777
- expect(state.cellData[id].lastCodeRun).toBe(newCodes[index]);
1778
- expect(state.cellData[id].edited).toBe(false);
1779
- });
1780
-
1781
- // When codeIsStale is true, lastCodeRun should not change
1782
- const staleCodes = ["stale1", "stale2", "stale3"];
1783
- actions.setCellCodes({
1784
- codes: staleCodes,
1785
- ids: newIds,
1786
- codeIsStale: true,
1787
- });
1788
- newIds.forEach((id, index) => {
1789
- expect(state.cellData[id].code).toBe(staleCodes[index]);
1790
- expect(state.cellData[id].lastCodeRun).toBe(newCodes[index]);
1791
- expect(state.cellData[id].edited).toBe(true);
1792
- });
1793
- });
1794
-
1795
- it("can can add a new cell with/without stale code", () => {
1796
- actions.setCellCodes({
1797
- codes: ["new code"],
1798
- ids: [cellId("2")],
1799
- codeIsStale: false,
1800
- });
1801
-
1802
- expect(state.cellData[cellId("2")].code).toBe("new code");
1803
- expect(state.cellData[cellId("2")].edited).toBe(false);
1804
- expect(state.cellData[cellId("2")].lastCodeRun).toBe("new code");
1805
-
1806
- actions.setCellCodes({
1807
- codes: ["new code 2"],
1808
- ids: [cellId("9")],
1809
- codeIsStale: true,
1810
- });
1811
-
1812
- expect(state.cellData[cellId("9")].code).toBe("new code 2");
1813
- expect(state.cellData[cellId("9")].edited).toBe(true);
1814
- expect(state.cellData[cellId("9")].lastCodeRun).toBe(null);
1815
- });
1816
-
1817
- it("can partial update cell codes", () => {
1818
- actions.createNewCell({ cellId: firstCellId, before: false });
1819
- actions.createNewCell({ cellId: cellId("1"), before: false });
1820
-
1821
- expect(state.cellIds.inOrderIds).toEqual([
1822
- cellId("0"),
1823
- cellId("1"),
1824
- cellId("2"),
1825
- ]);
1826
- expect(state.cellData[cellId("0")].code).toBe("");
1827
- expect(state.cellData[cellId("1")].code).toBe("");
1828
- expect(state.cellData[cellId("2")].code).toBe("");
1829
-
1830
- // Update cell 1
1831
- actions.setCellCodes({
1832
- codes: ["new code 2"],
1833
- ids: [cellId("1")],
1834
- codeIsStale: false,
1835
- });
1836
-
1837
- expect(state.cellIds.inOrderIds).toEqual([
1838
- cellId("0"),
1839
- cellId("1"),
1840
- cellId("2"),
1841
- ]);
1842
- expect(state.cellData[cellId("0")].code).toBe("");
1843
- expect(state.cellData[cellId("1")].code).toBe("new code 2");
1844
- expect(state.cellData[cellId("1")].edited).toBe(false);
1845
- expect(state.cellData[cellId("2")].code).toBe("");
1846
- });
1847
-
1848
- it("can set cell codes with new cell ids, while preserving the old cell data", () => {
1849
- actions.setCellCodes({
1850
- codes: ["code1", "code2", "code3"],
1851
- ids: [cellId("3"), cellId("4"), cellId("5")],
1852
- codeIsStale: false,
1853
- });
1854
- expect(state.cellData[cellId("3")].code).toBe("code1");
1855
- expect(state.cellData[cellId("4")].code).toBe("code2");
1856
- expect(state.cellData[cellId("5")].code).toBe("code3");
1857
-
1858
- // Update with some new cell ids and some old cell ids
1859
- actions.setCellIds({
1860
- cellIds: [cellId("1"), cellId("2"), cellId("3"), cellId("4")],
1861
- });
1862
- actions.setCellCodes({
1863
- codes: ["new1", "new2", "code1", "code2"],
1864
- ids: [cellId("1"), cellId("2"), cellId("3"), cellId("4")],
1865
- codeIsStale: false,
1866
- });
1867
- expect(state.cellData[cellId("1")].code).toBe("new1");
1868
- expect(state.cellData[cellId("2")].code).toBe("new2");
1869
- expect(state.cellData[cellId("3")].code).toBe("code1");
1870
- expect(state.cellData[cellId("4")].code).toBe("code2");
1871
- expect(state.cellIds.inOrderIds).toEqual([
1872
- cellId("1"),
1873
- cellId("2"),
1874
- cellId("3"),
1875
- cellId("4"),
1876
- ]);
1877
- // Cell 5 data is preserved (possibly used for tracing), but it's not in the cellIds
1878
- expect(state.cellData[cellId("5")]).not.toBeUndefined();
1879
- });
1880
-
1881
- it("can set cell codes with names and configs", () => {
1882
- const newIds = [cellId("3"), cellId("4")];
1883
- actions.setCellIds({ cellIds: newIds });
1884
- actions.setCellCodes({
1885
- codes: ["code1", "code2"],
1886
- ids: newIds,
1887
- codeIsStale: false,
1888
- names: ["setup_cell", "analysis"],
1889
- configs: [
1890
- { hide_code: true, disabled: false, column: null },
1891
- { hide_code: false, disabled: true, column: null },
1892
- ],
1893
- });
1894
-
1895
- expect(state.cellData[cellId("3")].name).toBe("setup_cell");
1896
- expect(state.cellData[cellId("3")].config.hide_code).toBe(true);
1897
- expect(state.cellData[cellId("3")].config.disabled).toBe(false);
1898
-
1899
- expect(state.cellData[cellId("4")].name).toBe("analysis");
1900
- expect(state.cellData[cellId("4")].config.hide_code).toBe(false);
1901
- expect(state.cellData[cellId("4")].config.disabled).toBe(true);
1902
- });
1903
-
1904
- it("can set cell codes without names/configs (backward compat)", () => {
1905
- const newIds = [cellId("3")];
1906
- actions.setCellIds({ cellIds: newIds });
1907
- actions.setCellCodes({
1908
- codes: ["code1"],
1909
- ids: newIds,
1910
- codeIsStale: false,
1911
- });
1912
-
1913
- // Should use defaults when names/configs not provided
1914
- expect(state.cellData[cellId("3")].code).toBe("code1");
1915
- expect(state.cellData[cellId("3")].config.hide_code).toBe(false);
1916
- expect(state.cellData[cellId("3")].config.disabled).toBe(false);
1917
- });
1918
-
1919
- it("can update names and configs on existing cells via setCellCodes", () => {
1920
- // Set initial state
1921
- actions.setCellCodes({
1922
- codes: ["x = 1"],
1923
- ids: [firstCellId],
1924
- codeIsStale: false,
1925
- names: ["old_name"],
1926
- configs: [{ hide_code: false, disabled: false, column: null }],
1927
- });
1928
- expect(state.cellData[firstCellId].name).toBe("old_name");
1929
- expect(state.cellData[firstCellId].config.hide_code).toBe(false);
1930
-
1931
- // Update with new name and config (same code)
1932
- actions.setCellCodes({
1933
- codes: ["x = 1"],
1934
- ids: [firstCellId],
1935
- codeIsStale: true,
1936
- names: ["new_name"],
1937
- configs: [{ hide_code: true, disabled: false, column: null }],
1938
- });
1939
- expect(state.cellData[firstCellId].name).toBe("new_name");
1940
- expect(state.cellData[firstCellId].config.hide_code).toBe(true);
1941
- });
1942
-
1943
- it("can fold and unfold all cells", () => {
1944
- actions.foldAll();
1945
- expect(foldAllBulk).toHaveBeenCalled();
1946
-
1947
- actions.unfoldAll();
1948
- expect(unfoldAllBulk).toHaveBeenCalled();
1949
- });
1494
+ actions.unfoldAll();
1495
+ expect(unfoldAllBulk).toHaveBeenCalled();
1496
+ });
1950
1497
 
1951
1498
  it("can clear logs", () => {
1952
1499
  state.cellLogs = [
@@ -1968,13 +1515,13 @@ describe("cell reducer", () => {
1968
1515
  });
1969
1516
 
1970
1517
  it("can collapse and expand cells", () => {
1971
- actions.createNewCell({ cellId: firstCellId, before: false });
1972
- actions.createNewCell({
1518
+ seedCell({ cellId: firstCellId, before: false });
1519
+ seedCell({
1973
1520
  cellId: cellId("1"),
1974
1521
  before: false,
1975
1522
  code: "# Header",
1976
1523
  });
1977
- actions.createNewCell({
1524
+ seedCell({
1978
1525
  cellId: cellId("2"),
1979
1526
  before: false,
1980
1527
  code: "## Subheader",
@@ -1996,13 +1543,13 @@ describe("cell reducer", () => {
1996
1543
  });
1997
1544
 
1998
1545
  it("can collapse and expand all cells in multiple columns", () => {
1999
- actions.createNewCell({ cellId: firstCellId, before: false });
2000
- actions.createNewCell({
1546
+ seedCell({ cellId: firstCellId, before: false });
1547
+ seedCell({
2001
1548
  cellId: cellId("1"),
2002
1549
  before: false,
2003
1550
  code: "# First Column Header",
2004
1551
  });
2005
- actions.createNewCell({
1552
+ seedCell({
2006
1553
  cellId: cellId("2"),
2007
1554
  before: false,
2008
1555
  code: "## First Column Subheader",
@@ -2010,12 +1557,12 @@ describe("cell reducer", () => {
2010
1557
 
2011
1558
  actions.addColumnBreakpoint({ cellId: cellId("2") });
2012
1559
 
2013
- actions.createNewCell({
1560
+ seedCell({
2014
1561
  cellId: cellId("3"),
2015
1562
  before: false,
2016
1563
  code: "# Second Column Header",
2017
1564
  });
2018
- actions.createNewCell({
1565
+ seedCell({
2019
1566
  cellId: cellId("4"),
2020
1567
  before: false,
2021
1568
  code: "## Second Column Subheader",
@@ -2063,18 +1610,18 @@ describe("cell reducer", () => {
2063
1610
  });
2064
1611
 
2065
1612
  it("can collapse and expand nested cells in one call", () => {
2066
- actions.createNewCell({ cellId: firstCellId, before: false });
2067
- actions.createNewCell({
1613
+ seedCell({ cellId: firstCellId, before: false });
1614
+ seedCell({
2068
1615
  cellId: cellId("1"),
2069
1616
  before: false,
2070
1617
  code: "# Header",
2071
1618
  });
2072
- actions.createNewCell({
1619
+ seedCell({
2073
1620
  cellId: cellId("2"),
2074
1621
  before: false,
2075
1622
  code: "## Subheader",
2076
1623
  });
2077
- actions.createNewCell({
1624
+ seedCell({
2078
1625
  cellId: cellId("3"),
2079
1626
  before: false,
2080
1627
  code: "### Subsubheader",
@@ -2118,18 +1665,18 @@ describe("cell reducer", () => {
2118
1665
  });
2119
1666
 
2120
1667
  it("can collapse all cells when nested heading ranges end at the same cell", () => {
2121
- actions.createNewCell({ cellId: firstCellId, before: false });
2122
- actions.createNewCell({
1668
+ seedCell({ cellId: firstCellId, before: false });
1669
+ seedCell({
2123
1670
  cellId: cellId("1"),
2124
1671
  before: false,
2125
1672
  code: "# Header",
2126
1673
  });
2127
- actions.createNewCell({
1674
+ seedCell({
2128
1675
  cellId: cellId("2"),
2129
1676
  before: false,
2130
1677
  code: "## Subheader",
2131
1678
  });
2132
- actions.createNewCell({
1679
+ seedCell({
2133
1680
  cellId: cellId("3"),
2134
1681
  before: false,
2135
1682
  code: "### Subsubheader",
@@ -2183,23 +1730,23 @@ describe("cell reducer", () => {
2183
1730
  });
2184
1731
 
2185
1732
  it("can collapse all cells when a deep chain is followed by a later section", () => {
2186
- actions.createNewCell({ cellId: firstCellId, before: false });
2187
- actions.createNewCell({
1733
+ seedCell({ cellId: firstCellId, before: false });
1734
+ seedCell({
2188
1735
  cellId: cellId("1"),
2189
1736
  before: false,
2190
1737
  code: "# Header",
2191
1738
  });
2192
- actions.createNewCell({
1739
+ seedCell({
2193
1740
  cellId: cellId("2"),
2194
1741
  before: false,
2195
1742
  code: "## Section A",
2196
1743
  });
2197
- actions.createNewCell({
1744
+ seedCell({
2198
1745
  cellId: cellId("3"),
2199
1746
  before: false,
2200
1747
  code: "### Sub of A",
2201
1748
  });
2202
- actions.createNewCell({
1749
+ seedCell({
2203
1750
  cellId: cellId("4"),
2204
1751
  before: false,
2205
1752
  code: "## Section B",
@@ -2262,8 +1809,8 @@ describe("cell reducer", () => {
2262
1809
  });
2263
1810
 
2264
1811
  it("can show hidden cells", () => {
2265
- actions.createNewCell({ cellId: firstCellId, before: false });
2266
- actions.createNewCell({ cellId: cellId("1"), before: false });
1812
+ seedCell({ cellId: firstCellId, before: false });
1813
+ seedCell({ cellId: cellId("1"), before: false });
2267
1814
  actions.collapseCell({ cellId: firstCellId });
2268
1815
 
2269
1816
  actions.showCellIfHidden({ cellId: cellId("1") });
@@ -2273,7 +1820,7 @@ describe("cell reducer", () => {
2273
1820
  });
2274
1821
 
2275
1822
  it("can split and undo split cells", () => {
2276
- actions.createNewCell({
1823
+ seedCell({
2277
1824
  cellId: firstCellId,
2278
1825
  before: false,
2279
1826
  code: "line1\nline2",
@@ -2340,9 +1887,9 @@ describe("cell reducer", () => {
2340
1887
  });
2341
1888
 
2342
1889
  it("can add a column breakpoint", () => {
2343
- actions.createNewCell({ cellId: firstCellId, before: false });
2344
- actions.createNewCell({ cellId: cellId("1"), before: false });
2345
- 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 });
2346
1893
 
2347
1894
  expect(state.cellIds.getColumns().length).toBe(1);
2348
1895
  expect(formatCells(state)).toMatchInlineSnapshot(`
@@ -2387,16 +1934,16 @@ describe("cell reducer", () => {
2387
1934
 
2388
1935
  it("cannot add a column breakpoint before the first cell", () => {
2389
1936
  expect(state.cellIds.getColumns().length).toBe(1);
2390
- actions.createNewCell({ cellId: firstCellId, before: false });
2391
- actions.createNewCell({ cellId: cellId("1"), before: false });
1937
+ seedCell({ cellId: firstCellId, before: false });
1938
+ seedCell({ cellId: cellId("1"), before: false });
2392
1939
  actions.addColumnBreakpoint({ cellId: firstCellId });
2393
1940
  expect(state.cellIds.getColumns().length).toBe(1);
2394
1941
  });
2395
1942
 
2396
1943
  it("can delete a column", () => {
2397
- actions.createNewCell({ cellId: firstCellId, before: false });
2398
- actions.createNewCell({ cellId: cellId("1"), before: false });
2399
- 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 });
2400
1947
  actions.addColumnBreakpoint({ cellId: cellId("2") });
2401
1948
 
2402
1949
  expect(state.cellIds.getColumns().length).toBe(2);
@@ -2440,8 +1987,8 @@ describe("cell reducer", () => {
2440
1987
  });
2441
1988
 
2442
1989
  it("deleting the last column does nothing", () => {
2443
- actions.createNewCell({ cellId: firstCellId, before: false });
2444
- actions.createNewCell({ cellId: cellId("1"), before: false });
1990
+ seedCell({ cellId: firstCellId, before: false });
1991
+ seedCell({ cellId: cellId("1"), before: false });
2445
1992
 
2446
1993
  const initialState = { ...state };
2447
1994
 
@@ -2452,9 +1999,9 @@ describe("cell reducer", () => {
2452
1999
  });
2453
2000
 
2454
2001
  it("can drop a cell over another cell", () => {
2455
- actions.createNewCell({ cellId: firstCellId, before: false });
2456
- actions.createNewCell({ cellId: cellId("1"), before: false });
2457
- 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 });
2458
2005
 
2459
2006
  expect(formatCells(state)).toMatchInlineSnapshot(`
2460
2007
  "
@@ -2487,8 +2034,8 @@ describe("cell reducer", () => {
2487
2034
  });
2488
2035
 
2489
2036
  it("can drop a cell over a new column", () => {
2490
- actions.createNewCell({ cellId: firstCellId, before: false });
2491
- actions.createNewCell({ cellId: cellId("1"), before: false });
2037
+ seedCell({ cellId: firstCellId, before: false });
2038
+ seedCell({ cellId: cellId("1"), before: false });
2492
2039
 
2493
2040
  expect(state.cellIds.getColumns().length).toBe(1);
2494
2041
  expect(formatCells(state)).toMatchInlineSnapshot(`
@@ -2525,9 +2072,9 @@ describe("cell reducer", () => {
2525
2072
  });
2526
2073
 
2527
2074
  it("can drop a column over another column", () => {
2528
- actions.createNewCell({ cellId: firstCellId, before: false });
2529
- actions.createNewCell({ cellId: cellId("1"), before: false });
2530
- 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 });
2531
2078
  actions.addColumnBreakpoint({ cellId: cellId("2") });
2532
2079
 
2533
2080
  expect(state.cellIds.getColumns().length).toBe(2);
@@ -2578,8 +2125,8 @@ describe("cell reducer", () => {
2578
2125
 
2579
2126
  it("can compact columns", () => {
2580
2127
  // Create initial state with 3 columns, including an empty one
2581
- actions.createNewCell({ cellId: firstCellId, before: false });
2582
- actions.createNewCell({ cellId: cellId("1"), before: false });
2128
+ seedCell({ cellId: firstCellId, before: false });
2129
+ seedCell({ cellId: cellId("1"), before: false });
2583
2130
  actions.addColumnBreakpoint({ cellId: cellId("1") });
2584
2131
  actions.addColumnBreakpoint({ cellId: cellId("2") });
2585
2132
  actions.dropOverNewColumn({ cellId: cellId("2") });
@@ -2630,116 +2177,6 @@ describe("cell reducer", () => {
2630
2177
  expect(state.cellIds.getColumns()[2].topLevelIds).toEqual([cellId("2")]);
2631
2178
  });
2632
2179
 
2633
- it("rebuildCellColumns regroups cells by config.column", () => {
2634
- // Create four cells in a single column.
2635
- actions.createNewCell({ cellId: firstCellId, before: false });
2636
- actions.createNewCell({ cellId: cellId("1"), before: false });
2637
- actions.createNewCell({ cellId: cellId("2"), before: false });
2638
- expect(state.cellIds.getColumns().length).toBe(1);
2639
-
2640
- // Explicitly set config.column on each cell. This ONLY updates metadata —
2641
- // updateCellConfig does not touch the MultiColumn tree.
2642
- actions.updateCellConfig({
2643
- cellId: cellId("0"),
2644
- config: { column: 0 },
2645
- });
2646
- actions.updateCellConfig({
2647
- cellId: cellId("2"),
2648
- config: { column: 1 },
2649
- });
2650
-
2651
- // Tree is still a single column at this point.
2652
- expect(state.cellIds.getColumns().length).toBe(1);
2653
-
2654
- // Now rebuild the column tree from metadata.
2655
- actions.rebuildCellColumns({
2656
- cellIds: [cellId("0"), cellId("1"), cellId("2"), cellId("3")],
2657
- });
2658
-
2659
- expect(state.cellIds.getColumns().length).toBe(2);
2660
- // Cell 1 inherits from 0 (col 0), cell 3 inherits from 2 (col 1).
2661
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
2662
- cellId("0"),
2663
- cellId("1"),
2664
- ]);
2665
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([
2666
- cellId("2"),
2667
- cellId("3"),
2668
- ]);
2669
- });
2670
-
2671
- it("rebuildCellColumns with explicit column on every cell", () => {
2672
- actions.createNewCell({ cellId: firstCellId, before: false });
2673
- actions.createNewCell({ cellId: cellId("1"), before: false });
2674
- actions.createNewCell({ cellId: cellId("2"), before: false });
2675
-
2676
- for (const [id, col] of [
2677
- ["0", 1],
2678
- ["1", 0],
2679
- ["2", 1],
2680
- ["3", 0],
2681
- ] as const) {
2682
- actions.updateCellConfig({ cellId: cellId(id), config: { column: col } });
2683
- }
2684
-
2685
- actions.rebuildCellColumns({
2686
- cellIds: [cellId("0"), cellId("1"), cellId("2"), cellId("3")],
2687
- });
2688
-
2689
- expect(state.cellIds.getColumns().length).toBe(2);
2690
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
2691
- cellId("1"),
2692
- cellId("3"),
2693
- ]);
2694
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([
2695
- cellId("0"),
2696
- cellId("2"),
2697
- ]);
2698
- });
2699
-
2700
- it("rebuildCellColumns collapses to one column when all cells are col 0", () => {
2701
- actions.createNewCell({ cellId: firstCellId, before: false });
2702
- actions.addColumnBreakpoint({ cellId: cellId("1") });
2703
- expect(state.cellIds.getColumns().length).toBe(2);
2704
-
2705
- // Wipe column metadata back to 0 for both cells.
2706
- actions.updateCellConfig({ cellId: cellId("0"), config: { column: 0 } });
2707
- actions.updateCellConfig({ cellId: cellId("1"), config: { column: 0 } });
2708
-
2709
- actions.rebuildCellColumns({
2710
- cellIds: [cellId("0"), cellId("1")],
2711
- });
2712
-
2713
- expect(state.cellIds.getColumns().length).toBe(1);
2714
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
2715
- cellId("0"),
2716
- cellId("1"),
2717
- ]);
2718
- });
2719
-
2720
- it("rebuildCellColumns follows the provided order, not current tree order", () => {
2721
- // Start with a single column.
2722
- actions.createNewCell({ cellId: firstCellId, before: false });
2723
- actions.createNewCell({ cellId: cellId("1"), before: false });
2724
- actions.updateCellConfig({ cellId: cellId("0"), config: { column: 0 } });
2725
- actions.updateCellConfig({ cellId: cellId("2"), config: { column: 1 } });
2726
-
2727
- // Pass the reversed order. The rebuild should honor it.
2728
- actions.rebuildCellColumns({
2729
- cellIds: [cellId("2"), cellId("1"), cellId("0")],
2730
- });
2731
-
2732
- // cellIds iteration:
2733
- // 2 → col=1, pushed to col1, prev=1
2734
- // 1 → col=null, pushed to col[prev=1] = col1
2735
- // 0 → col=0, pushed to col0
2736
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([cellId("0")]);
2737
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([
2738
- cellId("2"),
2739
- cellId("1"),
2740
- ]);
2741
- });
2742
-
2743
2180
  it("can clear output of a single cell", () => {
2744
2181
  // Set up initial state with output
2745
2182
  actions.handleCellMessage({
@@ -2828,7 +2265,7 @@ describe("cell reducer", () => {
2828
2265
 
2829
2266
  it("can clear output of all cells", () => {
2830
2267
  // Create multiple cells with output
2831
- actions.createNewCell({ cellId: firstCellId, before: false });
2268
+ seedCell({ cellId: firstCellId, before: false });
2832
2269
  const secondCellId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
2833
2270
 
2834
2271
  // Add output to both cells
@@ -2875,121 +2312,9 @@ describe("cell reducer", () => {
2875
2312
  expect(state.cellRuntime[secondCellId].consoleOutputs).toEqual([]);
2876
2313
  });
2877
2314
 
2878
- it("skips creating new cell if code exists and skipIfCodeExists is true", () => {
2879
- // Add initial cell with code
2880
- actions.updateCellCode({
2881
- cellId: firstCellId,
2882
- code: "import numpy as np",
2883
- formattingChange: false,
2884
- });
2885
-
2886
- // Try to create new cell with same code and skipIfCodeExists
2887
- actions.createNewCell({
2888
- cellId: "__end__",
2889
- code: "import numpy as np",
2890
- before: false,
2891
- skipIfCodeExists: true,
2892
- });
2893
-
2894
- // Should still only have one cell
2895
- expect(state.cellIds.inOrderIds.length).toBe(1);
2896
- expect(formatCells(state)).toMatchInlineSnapshot(`
2897
- "
2898
- [0] 'import numpy as np'
2899
- "
2900
- `);
2901
-
2902
- // Verify we can still add cell with different code
2903
- actions.createNewCell({
2904
- cellId: "__end__",
2905
- code: "import pandas as pd",
2906
- before: false,
2907
- skipIfCodeExists: true,
2908
- });
2909
-
2910
- expect(state.cellIds.inOrderIds.length).toBe(2);
2911
- expect(formatCells(state)).toMatchInlineSnapshot(`
2912
- "
2913
- [0] 'import numpy as np'
2914
-
2915
- [1] 'import pandas as pd'
2916
- "
2917
- `);
2918
- });
2919
-
2920
- it("can create and noop-update a setup cell", () => {
2921
- // Create the setup cell
2922
- actions.addSetupCellIfDoesntExist({ code: "# Setup code" });
2923
-
2924
- // Check that setup cell was created
2925
- expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
2926
- expect(state.cellData[SETUP_CELL_ID].name).toBe("setup");
2927
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
2928
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2929
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2930
-
2931
- // Update the setup cell
2932
- actions.addSetupCellIfDoesntExist({ code: "# Updated setup code" });
2933
-
2934
- // Check that the setup cell did not change, since it already exists
2935
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
2936
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2937
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2938
- });
2939
-
2940
- it("can delete and undelete the setup cell", () => {
2941
- // Create the setup cell
2942
- actions.addSetupCellIfDoesntExist({ code: "# Setup code" });
2943
-
2944
- // Check that setup cell was created
2945
- expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
2946
- expect(state.cellData[SETUP_CELL_ID].name).toBe("setup");
2947
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
2948
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2949
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2950
-
2951
- // Delete the setup cell
2952
- actions.deleteCell({ cellId: SETUP_CELL_ID });
2953
-
2954
- // Check that setup cell was deleted
2955
- expect(state.cellData[SETUP_CELL_ID]).toBeDefined(); // we keep old state
2956
- expect(state.cellIds.inOrderIds).not.toContain(SETUP_CELL_ID);
2957
-
2958
- // Undo delete the setup cell
2959
- actions.undoDeleteCell();
2960
-
2961
- // Check that setup cell was restored
2962
- expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
2963
- expect(state.cellData[SETUP_CELL_ID].name).toBe("setup");
2964
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
2965
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2966
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2967
-
2968
- // Verify scrollKey is set to the restored setup cell
2969
- expect(state.scrollKey).toBe(SETUP_CELL_ID);
2970
- });
2971
-
2972
- it("can delete and then create a new setup cell", () => {
2973
- // Create the setup cell
2974
- actions.addSetupCellIfDoesntExist({ code: "# Setup code" });
2975
-
2976
- // Delete the setup cell
2977
- actions.deleteCell({ cellId: SETUP_CELL_ID });
2978
-
2979
- // Create a new setup cell
2980
- actions.addSetupCellIfDoesntExist({ code: "# New code" });
2981
-
2982
- // Check that the new setup cell was created
2983
- expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
2984
- expect(state.cellData[SETUP_CELL_ID].name).toBe("setup");
2985
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# New code");
2986
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2987
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2988
- });
2989
-
2990
2315
  it("can clear all outputs", () => {
2991
2316
  // Add a cell and give it output
2992
- actions.createNewCell({
2317
+ seedCell({
2993
2318
  cellId: firstCellId,
2994
2319
  before: false,
2995
2320
  });
@@ -3017,202 +2342,14 @@ describe("cell reducer", () => {
3017
2342
  expect(state.cellRuntime[cell2Id].output).toBeNull();
3018
2343
  });
3019
2344
 
3020
- describe("moveToNextCell", () => {
3021
- let cell1Id: CellId;
3022
- let cell2Id: CellId;
3023
- let cell3Id: CellId;
3024
-
3025
- beforeEach(() => {
3026
- // Create a few cells to work with
3027
- actions.createNewCell({ cellId: "__end__", before: false });
3028
- actions.createNewCell({ cellId: "__end__", before: false });
3029
-
3030
- cell1Id = state.cellIds.inOrderIds[0];
3031
- cell2Id = state.cellIds.inOrderIds[1];
3032
- cell3Id = state.cellIds.inOrderIds[2];
3033
- });
3034
-
3035
- it("creates new cell when moving after last cell with noCreate=false", () => {
3036
- const initialCellCount = state.cellIds.inOrderIds.length;
3037
-
3038
- actions.moveToNextCell({
3039
- cellId: cell3Id,
3040
- before: false,
3041
- noCreate: false,
3042
- });
3043
-
3044
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount + 1);
3045
- });
3046
-
3047
- it("creates new cell when moving before first cell with noCreate=false", () => {
3048
- const initialCellCount = state.cellIds.inOrderIds.length;
3049
-
3050
- actions.moveToNextCell({
3051
- cellId: cell1Id,
3052
- before: true,
3053
- noCreate: false,
3054
- });
3055
-
3056
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount + 1);
3057
- });
3058
-
3059
- it("does not create new cell when moving after last cell with noCreate=true", () => {
3060
- const initialCellCount = state.cellIds.inOrderIds.length;
3061
- const initialState = { ...state };
3062
-
3063
- actions.moveToNextCell({
3064
- cellId: cell3Id,
3065
- before: false,
3066
- noCreate: true,
3067
- });
3068
-
3069
- // Should not create a new cell
3070
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount);
3071
- // Should not crash or throw an error
3072
- expect(state.cellIds.inOrderIds).toEqual(initialState.cellIds.inOrderIds);
3073
- });
3074
-
3075
- it("does not create new cell when moving before first cell with noCreate=true", () => {
3076
- const initialCellCount = state.cellIds.inOrderIds.length;
3077
- const initialState = { ...state };
3078
-
3079
- actions.moveToNextCell({ cellId: cell1Id, before: true, noCreate: true });
3080
-
3081
- // Should not create a new cell
3082
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount);
3083
- // Should not crash or throw an error
3084
- expect(state.cellIds.inOrderIds).toEqual(initialState.cellIds.inOrderIds);
3085
- });
3086
-
3087
- it("focuses next cell when moving within bounds", () => {
3088
- const focusSpy = vi.mocked(focusAndScrollCellIntoView);
3089
- focusSpy.mockClear();
3090
-
3091
- actions.moveToNextCell({
3092
- cellId: cell1Id,
3093
- before: false,
3094
- noCreate: true,
3095
- });
3096
-
3097
- expect(focusSpy).toHaveBeenCalledWith({
3098
- cellId: cell2Id,
3099
- cell: state.cellHandles[cell2Id],
3100
- isCodeHidden: false,
3101
- codeFocus: "top",
3102
- variableName: undefined,
3103
- });
3104
- });
3105
-
3106
- it("focuses previous cell when moving backward within bounds", () => {
3107
- const focusSpy = vi.mocked(focusAndScrollCellIntoView);
3108
- focusSpy.mockClear();
3109
-
3110
- actions.moveToNextCell({ cellId: cell2Id, before: true, noCreate: true });
3111
-
3112
- expect(focusSpy).toHaveBeenCalledWith({
3113
- cellId: cell1Id,
3114
- cell: state.cellHandles[cell1Id],
3115
- isCodeHidden: false,
3116
- codeFocus: "bottom",
3117
- variableName: undefined,
3118
- });
3119
- });
3120
-
3121
- it("does not move focus from scratch cell", () => {
3122
- const focusSpy = vi.mocked(focusAndScrollCellIntoView);
3123
- focusSpy.mockClear();
3124
- const initialState = { ...state };
3125
-
3126
- actions.moveToNextCell({
3127
- cellId: SCRATCH_CELL_ID,
3128
- before: false,
3129
- noCreate: false,
3130
- });
3131
-
3132
- expect(focusSpy).not.toHaveBeenCalled();
3133
- expect(state.cellIds.inOrderIds).toEqual(initialState.cellIds.inOrderIds);
3134
- });
3135
- });
3136
-
3137
2345
  describe("untouched cells functionality", () => {
3138
2346
  it("starts with empty untouchedNewCells set", () => {
3139
2347
  expect(state.untouchedNewCells).toEqual(new Set());
3140
2348
  });
3141
2349
 
3142
- it("can create a new cell with hideCode option", () => {
3143
- const initialCellCount = state.cellIds.inOrderIds.length;
3144
-
3145
- actions.createNewCell({
3146
- cellId: "__end__",
3147
- before: false,
3148
- hideCode: true,
3149
- });
3150
-
3151
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount + 1);
3152
- const newCellId =
3153
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3154
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3155
- });
3156
-
3157
- it("adds interactively-created hidden cell with boilerplate code to untouchedNewCells", () => {
3158
- // Markdown cells are created with hideCode and non-empty default code
3159
- // (e.g. `mo.md(r"""\n""")`). They are user-initiated, so their editor
3160
- // should be shown until first blur.
3161
- actions.createNewCell({
3162
- cellId: "__end__",
3163
- before: false,
3164
- code: 'mo.md(r"""\n""")',
3165
- hideCode: true,
3166
- autoFocus: true,
3167
- });
3168
-
3169
- const newCellId =
3170
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3171
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3172
- });
3173
-
3174
- it("does not add programmatically-created hidden cell with code to untouchedNewCells", () => {
3175
- // Cells created by the kernel (e.g. via code_mode) carry code and
3176
- // autoFocus=false; their hide_code must take effect immediately.
3177
- actions.createNewCell({
3178
- cellId: "__end__",
3179
- before: false,
3180
- code: "x = 1",
3181
- hideCode: true,
3182
- autoFocus: false,
3183
- });
3184
-
3185
- const newCellId =
3186
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3187
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3188
- });
3189
-
3190
- it("does not add cell to untouchedNewCells when hideCode is false", () => {
3191
- actions.createNewCell({
3192
- cellId: "__end__",
3193
- before: false,
3194
- hideCode: false,
3195
- });
3196
-
3197
- const newCellId =
3198
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3199
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3200
- });
3201
-
3202
- it("does not add cell to untouchedNewCells when hideCode is undefined", () => {
3203
- actions.createNewCell({
3204
- cellId: "__end__",
3205
- before: false,
3206
- });
3207
-
3208
- const newCellId =
3209
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3210
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3211
- });
3212
-
3213
2350
  it("can mark a cell as touched", () => {
3214
2351
  // Create a cell with hideCode
3215
- actions.createNewCell({
2352
+ seedCell({
3216
2353
  cellId: "__end__",
3217
2354
  before: false,
3218
2355
  hideCode: true,
@@ -3230,7 +2367,7 @@ describe("cell reducer", () => {
3230
2367
 
3231
2368
  it("markTouched is idempotent", () => {
3232
2369
  // Create a cell with hideCode
3233
- actions.createNewCell({
2370
+ seedCell({
3234
2371
  cellId: "__end__",
3235
2372
  before: false,
3236
2373
  hideCode: true,
@@ -3260,17 +2397,17 @@ describe("cell reducer", () => {
3260
2397
 
3261
2398
  it("can handle multiple untouched cells", () => {
3262
2399
  // Create multiple cells with hideCode
3263
- actions.createNewCell({
2400
+ seedCell({
3264
2401
  cellId: "__end__",
3265
2402
  before: false,
3266
2403
  hideCode: true,
3267
2404
  });
3268
- actions.createNewCell({
2405
+ seedCell({
3269
2406
  cellId: "__end__",
3270
2407
  before: false,
3271
2408
  hideCode: true,
3272
2409
  });
3273
- actions.createNewCell({
2410
+ seedCell({
3274
2411
  cellId: "__end__",
3275
2412
  before: false,
3276
2413
  hideCode: false,
@@ -3295,12 +2432,12 @@ describe("cell reducer", () => {
3295
2432
 
3296
2433
  it("preserves untouched state when cells are moved", () => {
3297
2434
  // Create cells with hideCode
3298
- actions.createNewCell({
2435
+ seedCell({
3299
2436
  cellId: "__end__",
3300
2437
  before: false,
3301
2438
  hideCode: true,
3302
2439
  });
3303
- actions.createNewCell({
2440
+ seedCell({
3304
2441
  cellId: "__end__",
3305
2442
  before: false,
3306
2443
  hideCode: true,
@@ -3324,58 +2461,9 @@ describe("cell reducer", () => {
3324
2461
  expect(state.untouchedNewCells.has(cell2Id)).toBe(true);
3325
2462
  });
3326
2463
 
3327
- it("does not remove untouched state when cell is deleted", () => {
3328
- // We could implement this, but it doesn't actually affect downstream behavior.
3329
- // It is easier to leave it, so `undo` works as expected.
3330
-
3331
- // Create a cell with hideCode
3332
- actions.createNewCell({
3333
- cellId: "__end__",
3334
- before: false,
3335
- hideCode: true,
3336
- });
3337
-
3338
- const newCellId =
3339
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3340
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3341
-
3342
- // Delete the cell
3343
- actions.deleteCell({ cellId: newCellId });
3344
-
3345
- // Does not actually remove untouched state
3346
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3347
- });
3348
-
3349
- it("restores untouched state when cell deletion is undone", () => {
3350
- // Create a cell with hideCode
3351
- actions.createNewCell({
3352
- cellId: "__end__",
3353
- before: false,
3354
- hideCode: true,
3355
- });
3356
-
3357
- const newCellId =
3358
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3359
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3360
-
3361
- // Delete the cell
3362
- actions.deleteCell({ cellId: newCellId });
3363
- // Still exists in untouchedNewCells
3364
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3365
-
3366
- // Undo the deletion
3367
- actions.undoDeleteCell();
3368
-
3369
- // The cell should be restored but no longer untouched
3370
- // (this is expected behavior - undoing doesn't restore untouched state)
3371
- const restoredCellId =
3372
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3373
- expect(state.untouchedNewCells.has(restoredCellId)).toBe(false);
3374
- });
3375
-
3376
2464
  it("untouched cell behavior with hide_code config", () => {
3377
2465
  // Create a cell with hideCode and hide_code config
3378
- actions.createNewCell({
2466
+ seedCell({
3379
2467
  cellId: "__end__",
3380
2468
  before: false,
3381
2469
  hideCode: true,
@@ -3384,11 +2472,11 @@ describe("cell reducer", () => {
3384
2472
  const newCellId =
3385
2473
  state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3386
2474
 
3387
- // Update the cell config to hide code
3388
- actions.updateCellConfig({
3389
- cellId: newCellId,
3390
- config: { hide_code: true },
3391
- });
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
+ };
3392
2480
 
3393
2481
  // Cell should be untouched and have hide_code config
3394
2482
  expect(state.untouchedNewCells.has(newCellId)).toBe(true);
@@ -3407,7 +2495,7 @@ describe("cell reducer", () => {
3407
2495
 
3408
2496
  it("can mark an existing cell as untouched", () => {
3409
2497
  // Create a cell without hideCode (not in untouchedNewCells)
3410
- actions.createNewCell({
2498
+ seedCell({
3411
2499
  cellId: "__end__",
3412
2500
  before: false,
3413
2501
  hideCode: false,
@@ -3425,7 +2513,7 @@ describe("cell reducer", () => {
3425
2513
 
3426
2514
  it("markUntouched is idempotent", () => {
3427
2515
  // Create a cell without hideCode
3428
- actions.createNewCell({
2516
+ seedCell({
3429
2517
  cellId: "__end__",
3430
2518
  before: false,
3431
2519
  hideCode: false,
@@ -3445,7 +2533,7 @@ describe("cell reducer", () => {
3445
2533
 
3446
2534
  it("markUntouched does not affect already untouched cells", () => {
3447
2535
  // Create a cell with hideCode (already in untouchedNewCells)
3448
- actions.createNewCell({
2536
+ seedCell({
3449
2537
  cellId: "__end__",
3450
2538
  before: false,
3451
2539
  hideCode: true,
@@ -3463,7 +2551,7 @@ describe("cell reducer", () => {
3463
2551
 
3464
2552
  it("markTouched and markUntouched can toggle cell state", () => {
3465
2553
  // Create a cell without hideCode
3466
- actions.createNewCell({
2554
+ seedCell({
3467
2555
  cellId: "__end__",
3468
2556
  before: false,
3469
2557
  hideCode: false,
@@ -3491,7 +2579,7 @@ describe("cell reducer", () => {
3491
2579
  it("markUntouched works for markdown cell conversion scenario", () => {
3492
2580
  // Simulates converting a Python cell to Markdown
3493
2581
  // 1. Create a regular cell (no hideCode)
3494
- actions.createNewCell({
2582
+ seedCell({
3495
2583
  cellId: "__end__",
3496
2584
  before: false,
3497
2585
  hideCode: false,
@@ -3505,11 +2593,12 @@ describe("cell reducer", () => {
3505
2593
  expect(state.untouchedNewCells.has(cellId)).toBe(false);
3506
2594
  expect(exportedForTesting.isCellCodeHidden(state, cellId)).toBe(false);
3507
2595
 
3508
- // 2. Convert to markdown: set hide_code and mark as untouched
3509
- actions.updateCellConfig({
3510
- cellId,
3511
- config: { hide_code: true },
3512
- });
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
+ };
3513
2602
  actions.markUntouched({ cellId });
3514
2603
 
3515
2604
  // Code should NOT be hidden because cell is untouched (user can edit)
@@ -3531,7 +2620,7 @@ describe("cell reducer", () => {
3531
2620
  const { cellDataAtom, cellRuntimeAtom, cellHandleAtom } =
3532
2621
  exportedForTesting;
3533
2622
 
3534
- actions.createNewCell({ cellId: firstCellId, before: false });
2623
+ seedCell({ cellId: firstCellId, before: false });
3535
2624
  const newCellId = state.cellIds.inOrderIds[1];
3536
2625
 
3537
2626
  const dataAtom1 = cellDataAtom(newCellId);
@@ -3547,31 +2636,6 @@ describe("cell reducer", () => {
3547
2636
  expect(runtimeAtom2).toBe(runtimeAtom1);
3548
2637
  expect(handleAtom2).toBe(handleAtom1);
3549
2638
  });
3550
-
3551
- it("cleans up atom family cache when cells are deleted", () => {
3552
- const { cellDataAtom, cellRuntimeAtom, cellHandleAtom } =
3553
- exportedForTesting;
3554
-
3555
- actions.createNewCell({ cellId: firstCellId, before: false });
3556
- const newCellId = state.cellIds.inOrderIds[1];
3557
-
3558
- // Access to create cache entries
3559
- const dataAtom1 = cellDataAtom(newCellId);
3560
- const runtimeAtom1 = cellRuntimeAtom(newCellId);
3561
- const handleAtom1 = cellHandleAtom(newCellId);
3562
-
3563
- // Triggers purge
3564
- actions.deleteCell({ cellId: newCellId });
3565
-
3566
- // Access again (should be new instances after cleanup)
3567
- const dataAtom2 = cellDataAtom(newCellId);
3568
- const runtimeAtom2 = cellRuntimeAtom(newCellId);
3569
- const handleAtom2 = cellHandleAtom(newCellId);
3570
-
3571
- expect(dataAtom2).not.toBe(dataAtom1);
3572
- expect(runtimeAtom2).not.toBe(runtimeAtom1);
3573
- expect(handleAtom2).not.toBe(handleAtom1);
3574
- });
3575
2639
  });
3576
2640
  });
3577
2641
 
@@ -3886,31 +2950,9 @@ describe("createTracebackInfoAtom", () => {
3886
2950
  });
3887
2951
  });
3888
2952
 
3889
- describe("setCells snapshot preservation", () => {
2953
+ describe("kernel-ready", () => {
3890
2954
  const CELL_A = cellId("A");
3891
2955
  const CELL_B = cellId("B");
3892
- const newCells: CellData[] = [
3893
- {
3894
- id: CELL_A,
3895
- name: "a",
3896
- code: "1",
3897
- edited: false,
3898
- lastCodeRun: null,
3899
- lastExecutionTime: null,
3900
- config: { hide_code: false, disabled: false, column: null },
3901
- serializedEditorState: null,
3902
- },
3903
- {
3904
- id: CELL_B,
3905
- name: "b",
3906
- code: "2",
3907
- edited: false,
3908
- lastCodeRun: null,
3909
- lastExecutionTime: null,
3910
- config: { hide_code: false, disabled: false, column: null },
3911
- serializedEditorState: null,
3912
- },
3913
- ];
3914
2956
 
3915
2957
  const hydratedState = () =>
3916
2958
  MockNotebook.notebookState({
@@ -3940,44 +2982,97 @@ describe("setCells snapshot preservation", () => {
3940
2982
  },
3941
2983
  });
3942
2984
 
3943
- beforeEach(async () => {
3944
- const { isWasm } = await import("@/core/wasm/utils");
3945
- 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);
3946
3006
  });
3947
3007
 
3948
- it("preserves hydrated output in WASM", () => {
3008
+ it("resetCellRuntimes starts every cell's runtime over", () => {
3949
3009
  const next = exportedForTesting.reducer(hydratedState(), {
3950
- type: "setCells",
3951
- payload: newCells,
3010
+ type: "resetCellRuntimes",
3011
+ payload: undefined,
3952
3012
  });
3953
3013
 
3954
- expect(next.cellRuntime[CELL_A].output).toMatchObject({
3955
- 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
+ },
3031
+ });
3032
+ const next = exportedForTesting.reducer(state, {
3033
+ type: "resetCellRuntimes",
3034
+ payload: undefined,
3956
3035
  });
3036
+
3037
+ expect(next.cellRuntime[SCRATCH_CELL_ID].output).toBeNull();
3957
3038
  });
3958
3039
 
3959
- it("preserves console-only hydration in WASM", () => {
3960
- const next = exportedForTesting.reducer(hydratedState(), {
3961
- type: "setCells",
3962
- payload: newCells,
3040
+ describe("resetCellRuntimes in WASM", () => {
3041
+ beforeEach(async () => {
3042
+ const { isWasm } = await import("@/core/wasm/utils");
3043
+ vi.mocked(isWasm).mockReturnValue(true);
3963
3044
  });
3964
3045
 
3965
- expect(next.cellRuntime[CELL_B].consoleOutputs).toHaveLength(1);
3966
- expect(next.cellRuntime[CELL_B].consoleOutputs[0]).toMatchObject({
3967
- data: "hydrated-B-stdout",
3046
+ afterEach(async () => {
3047
+ const { isWasm } = await import("@/core/wasm/utils");
3048
+ vi.mocked(isWasm).mockReturnValue(false);
3968
3049
  });
3969
- });
3970
3050
 
3971
- it("resets cells with no prior runtime even in WASM", () => {
3972
- const empty = MockNotebook.notebookState({ cellData: {} });
3973
- const next = exportedForTesting.reducer(empty, {
3974
- type: "setCells",
3975
- 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
+ });
3976
3063
  });
3977
3064
 
3978
- expect(next.cellRuntime[CELL_A].output).toBeNull();
3979
- expect(next.cellRuntime[CELL_A].consoleOutputs).toEqual([]);
3980
- expect(next.cellRuntime[CELL_B].output).toBeNull();
3981
- 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
+ });
3982
3077
  });
3983
3078
  });