@marimo-team/islands 0.25.3-dev31 → 0.25.3-dev311

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 (1036) hide show
  1. package/dist/{ConnectedDataExplorerComponent-7EaP9VWb.js → ConnectedDataExplorerComponent-DieU6L4x.js} +153 -177
  2. package/dist/{ErrorBoundary-DrVykO0s.js → ErrorBoundary-DdteSMhq.js} +79 -85
  3. package/dist/{ImageComparisonComponent-uU8FSKZE.js → ImageComparisonComponent-DgvSuY9B.js} +20 -20
  4. package/dist/{Plot-DdYOe1UK.js → Plot-Bfd0pJea.js} +2474 -3107
  5. package/dist/_baseSet-Bd5zh2yg.js +327 -0
  6. package/dist/{abnfDiagram-VCTEODGH-z_OSynK8.js → abnfDiagram-VCTEODGH-LOVrjbdp.js} +5 -5
  7. package/dist/any-language-editor-DMC2JE9_.js +75 -0
  8. package/dist/{apl-GEW9-ZP1.js → apl-stFrI2NB.js} +1 -1
  9. package/dist/{arc-7ra3iNdN.js → arc-4W6H62B5.js} +1 -1
  10. package/dist/architecture-7GRP2DOG-GlkXha5_.js +2 -0
  11. package/dist/{architectureDiagram-5GKGNRK7-Cs8J8BZr.js → architectureDiagram-5GKGNRK7-RZsm312h.js} +48 -64
  12. package/dist/{arrays-CN1djC4k.js → arrays-DoQ-AILx.js} +15 -27
  13. package/dist/{asciiarmor-BWNxzkG7.js → asciiarmor-DHwFuaIC.js} +1 -1
  14. package/dist/{asn1-Bcn2QutK.js → asn1-KIH4fwAq.js} +1 -1
  15. package/dist/assertNever-efcK_v3t.js +507 -0
  16. package/dist/assets/__vite-browser-external-BSp9tJ_k.js +1 -0
  17. package/dist/assets/worker-BdfPA0g5.js +171 -0
  18. package/dist/{blockDiagram-I7D4REHJ-Cg25UrxT.js → blockDiagram-I7D4REHJ-B4cvqWJn.js} +28 -39
  19. package/dist/{brainfuck-BQCYDheZ.js → brainfuck-UPAij4U9.js} +1 -1
  20. package/dist/{button-CvjkwdSk.js → button-BuSV-mLI.js} +64 -64
  21. package/dist/{c4Diagram-7LVT6UL2-BxFgrnNV.js → c4Diagram-7LVT6UL2-k-4SLDdM.js} +8 -8
  22. package/dist/{capabilities-CQ5IjhzL.js → capabilities-BiN90k7v.js} +2 -2
  23. package/dist/{channel-CPObtJXj.js → channel-CuTaWMxU.js} +1 -1
  24. package/dist/{chat-ui-BeKQyQQI.js → chat-ui-DkuxGEDp.js} +2153 -1880
  25. package/dist/{check-BMKBBrdc.js → check-DCQEEsUn.js} +1 -1
  26. package/dist/{chunk-4HAMMTFA-D4eFPyFh.js → chunk-4HAMMTFA-Bk_5S_m9.js} +6 -6
  27. package/dist/{chunk-75Z2AOVW-D7gxIs2Q.js → chunk-75Z2AOVW-9jQIEAxd.js} +4 -4
  28. package/dist/{chunk-DU6HZSFF-BwiyaNpg.js → chunk-DU6HZSFF-B20LZ-hE.js} +14 -22
  29. package/dist/{chunk-F27PBJKO-ufseIn5y.js → chunk-F27PBJKO-YjRXlNkT.js} +3 -3
  30. package/dist/{chunk-FOHPRMQF-BpYR8RqP.js → chunk-FOHPRMQF-ezmr_yWy.js} +49 -58
  31. package/dist/{chunk-GMAD6QVW-BhaJx0X3.js → chunk-GMAD6QVW-DokpSFdf.js} +16 -75
  32. package/dist/{chunk-GVQU2GXP-Cwbt-lF0.js → chunk-GVQU2GXP-DGvySV5W.js} +3 -3
  33. package/dist/{chunk-IMKFNOWR-aoSFasqF.js → chunk-IMKFNOWR-B6agbLjo.js} +8 -8
  34. package/dist/{chunk-L3NEJ4N5-n-zgIVSy.js → chunk-L3NEJ4N5-D9-ekjZc.js} +6 -6
  35. package/dist/{chunk-OSK3NFVY-DXa-Cxbn.js → chunk-OSK3NFVY-p7meGiY-.js} +8 -8
  36. package/dist/{chunk-P2QGCYS3-B_Zd3-X0.js → chunk-P2QGCYS3-74rxrEwb.js} +1 -1
  37. package/dist/{chunk-POPQ4Y6H-hYOXbYH_.js → chunk-POPQ4Y6H-EOTPk9Tj.js} +2 -2
  38. package/dist/{chunk-PWAF6VOD-CQw5qATZ.js → chunk-PWAF6VOD-oT_Qd2OU.js} +2 -2
  39. package/dist/{chunk-SHT3W25Y-CMaybO3O.js → chunk-SHT3W25Y-CfULafYG.js} +10 -10
  40. package/dist/{chunk-SVP7TREG-BNBrTAw1.js → chunk-SVP7TREG-DEOpPMJp.js} +3 -3
  41. package/dist/{chunk-TICWLB2K-DXNxvUJn.js → chunk-TICWLB2K-DaH-gkX5.js} +10 -10
  42. package/dist/{chunk-XXDRQBXY-Dnl5VBqG.js → chunk-XXDRQBXY-BM-YIt-_.js} +1 -1
  43. package/dist/{chunk-YOKDWASO-DKplTrBY.js → chunk-YOKDWASO-KxncHymC.js} +153 -378
  44. package/dist/classDiagram-ZZMXUADV-C0CyiWQ7.js +30 -0
  45. package/dist/classDiagram-v2-VYDZK3BY-xfIc9e2Y.js +30 -0
  46. package/dist/{clike-CxK9W55Z.js → clike-xU8sCVU1.js} +2 -6
  47. package/dist/{clojure-Dzcvwpxk.js → clojure-psoSEo36.js} +1 -1
  48. package/dist/{cmake-DYr-71zp.js → cmake-DDHBwlE7.js} +1 -1
  49. package/dist/{cobol-DC3g37Kq.js → cobol-C4qjDcIq.js} +1 -1
  50. package/dist/code-visibility-BzqsfUpE.js +131 -0
  51. package/dist/{coffeescript-C-eFwH5N.js → coffeescript-D6UgrGgo.js} +1 -1
  52. package/dist/{common-C6uebScP.js → common-CfI-FyP-.js} +10653 -10149
  53. package/dist/{commonlisp-CyaNXrJR.js → commonlisp-B0ChVjnu.js} +1 -1
  54. package/dist/{compiler-runtime-CldRdSsv.js → compiler-runtime-DNezxW8I.js} +2 -2
  55. package/dist/config-BVYA1P0C.js +660 -0
  56. package/dist/{copy-icon-BgUO8jsi.js → copy-icon-oHFcxX_y.js} +21 -22
  57. package/dist/{cose-bilkent-JH36ORCC-_LyRKVqm.js → cose-bilkent-JH36ORCC-B_Oz4vMP.js} +58 -71
  58. package/dist/createReducer-DHfURLCk.js +67 -0
  59. package/dist/{crystal-ih5ax5tQ.js → crystal-DZsc9Ue0.js} +1 -1
  60. package/dist/{css-CN_wP7_R.js → css-DkYRnQ9U.js} +1 -1
  61. package/dist/{cubic-OaEBhQPK.js → cubic-CA80xY_e.js} +6 -7
  62. package/dist/cynefin-OW5HDTMX-0-HehVvH.js +2 -0
  63. package/dist/{cynefinDiagram-5FMLGOSQ-DWHuKJs8.js → cynefinDiagram-5FMLGOSQ-DwGYhKu0.js} +5 -5
  64. package/dist/{cypher-CqGRcmZo.js → cypher-CpWPqgvy.js} +1 -1
  65. package/dist/{cytoscape.esm-jJhXJpLB.js → cytoscape.esm-BbeMznK5.js} +411 -512
  66. package/dist/{d-Cj0sOW2z.js → d-Bx2ggfYO.js} +1 -1
  67. package/dist/{dagre-BGSfXNEb.js → dagre-CpjuekF1.js} +550 -700
  68. package/dist/{dagre-GXQ25YYZ-DYLu-OCU.js → dagre-GXQ25YYZ-DfMaWJOV.js} +14 -16
  69. package/dist/{data-grid-overlay-editor-DhdeVwuk.js → data-grid-overlay-editor-BM-WjDgA.js} +5 -5
  70. package/dist/{data-grid-types-0PJ-9N5n.js → data-grid-types-CSElxhxo.js} +1 -1
  71. package/dist/{debounce-C3jyATpc.js → debounce-DrhhziJv.js} +2 -2
  72. package/dist/{diagram-S7CK7UJ4-PwwcUOHP.js → diagram-S7CK7UJ4-Bw_L87GZ.js} +7 -15
  73. package/dist/{diagram-UQ7AKVKN-CvJbL_IM.js → diagram-UQ7AKVKN-nsrs3Fbz.js} +5 -5
  74. package/dist/{diagram-VSXAHHWV-BJWcVVJY.js → diagram-VSXAHHWV-CodTjaL3.js} +6 -6
  75. package/dist/{diagram-VX7I27RA-B5RkqnzN.js → diagram-VX7I27RA-7ST9lEqP.js} +7 -7
  76. package/dist/{diagram-Z3DM3KII-CyijwTR2.js → diagram-Z3DM3KII-BnGte-HX.js} +5 -5
  77. package/dist/{dialog-Cc479ZEH.js → dialog-BIUdMLIS.js} +36 -36
  78. package/dist/{diff-B_hUkiat.js → diff-DNXaJS8v.js} +1 -1
  79. package/dist/{dist-D6d86eNJ.js → dist-B-Id1kt3.js} +48 -47
  80. package/dist/{dist-DiDibOYr.js → dist-BDw9g6PC.js} +152 -151
  81. package/dist/{dist-diIx5qJW.js → dist-B_EswY8r.js} +33 -32
  82. package/dist/{dist-DoE4XJBC.js → dist-B_xgcU0N.js} +9800 -10654
  83. package/dist/{dist-CLdpGQVr.js → dist-BcGGwW1q.js} +119 -121
  84. package/dist/dist-BdZQUzPS.js +850 -0
  85. package/dist/{dist-CN_BeG2I.js → dist-Bj8s59mu.js} +59 -58
  86. package/dist/{dist-CkTTIdBe.js → dist-C3dYgyiI.js} +337 -337
  87. package/dist/{dist-PXYI_yuP.js → dist-C6ttp2Iy.js} +24 -23
  88. package/dist/{dist-DUAh96QY.js → dist-CB9MzCXv.js} +1 -1
  89. package/dist/{dist-CSbh1PNP.js → dist-CCYz0qJ2.js} +24 -23
  90. package/dist/{dist-BMQf5zIS.js → dist-CDjivQ67.js} +124 -123
  91. package/dist/{dist-D-wwqlzJ.js → dist-CNT4u03u.js} +159 -158
  92. package/dist/{dist-F8Ep36rk.js → dist-DVVzH9mP.js} +60 -59
  93. package/dist/{dist-OR1OnN-y.js → dist-DlniJvh2.js} +32 -31
  94. package/dist/{dist-CSJ0MDRw.js → dist-Dq-hanx8.js} +57 -56
  95. package/dist/{dist-Lm-D-Vr6.js → dist-Du4PgaYp.js} +2 -2
  96. package/dist/{dist-C8fAKSH1.js → dist-FakvNY2e.js} +54 -53
  97. package/dist/{dist-BuaRHxOc.js → dist-K-qmZv0f.js} +45 -44
  98. package/dist/{dist-Cg7gzw-a.js → dist-NPImE_g9.js} +79 -78
  99. package/dist/{dist-CN8zI-XC.js → dist-PKX4HaX5.js} +239 -239
  100. package/dist/{dist-CrSP_H1M.js → dist-U4WlxEDL.js} +243 -243
  101. package/dist/{dist-BK4Oj99o.js → dist-UKKHsqHo.js} +19 -18
  102. package/dist/{dist-Bvs24R_E.js → dist-VRN9rDqa.js} +32 -31
  103. package/dist/{dist-BorMD-uX.js → dist-f1h5p-fp.js} +76 -75
  104. package/dist/{dist-BLfKmR-l.js → dist-h8Te4SvD.js} +17 -16
  105. package/dist/{dist-DBtSl3sf.js → dist-ymIUQ4tH.js} +48 -50
  106. package/dist/dropdown-menu-CAejOChE.js +5982 -0
  107. package/dist/{dtd-B8FGSWWe.js → dtd-D1YZQCVT.js} +1 -1
  108. package/dist/{dylan-DbIBYIrw.js → dylan-CxbG-PUm.js} +2 -2
  109. package/dist/{ebnfDiagram-PWID7BFC-DG90ELFp.js → ebnfDiagram-PWID7BFC-zaKyJ1YL.js} +5 -5
  110. package/dist/{ecl-DBmkdWAd.js → ecl-DIXH6vul.js} +1 -1
  111. package/dist/{eiffel-CcWSDDzK.js → eiffel-D0de-eQr.js} +1 -1
  112. package/dist/{elm-MC-b8a6C.js → elm-DrQrn4VD.js} +1 -1
  113. package/dist/{erDiagram-RLTQ6QDP-FMo_SpyX.js → erDiagram-RLTQ6QDP-Cx7g95FZ.js} +14 -14
  114. package/dist/{erlang-AdO1-xtV.js → erlang-2KpKpfC7.js} +1 -1
  115. package/dist/{error-banner-CFPHFw4h.js → error-banner-DkQNU4bn.js} +146 -146
  116. package/dist/error-entries-istW7Xbs.js +2626 -0
  117. package/dist/{esm-G4Fhsgng.js → esm-BUoz9Vvv.js} +4 -4
  118. package/dist/{esm-AMFssoNp.js → esm-BvhFNYzM.js} +333 -333
  119. package/dist/{esm-D2gTsXEV.js → esm-wKplZtOX.js} +580 -581
  120. package/dist/eventmodeling-NTZA5JFV-DoBvB4Lj.js +2 -0
  121. package/dist/{factor-Ys-13HOP.js → factor-Bwu3ZGOk.js} +1 -1
  122. package/dist/feedback-button-BGtR35Ev.js +371 -0
  123. package/dist/flowDiagram-HODETNUW-C7nIqaHP.js +15 -0
  124. package/dist/{formats-DRHUWwSK.js → formats-CMa7L3E8.js} +9 -9
  125. package/dist/{forth-DiMuIXFe.js → forth-DYatDpr6.js} +2 -3
  126. package/dist/{fortran-hkrF2eM4.js → fortran-n8mcQPkL.js} +1 -1
  127. package/dist/{ganttDiagram-EL5Y4UJY-Dh_4cBJZ.js → ganttDiagram-EL5Y4UJY-DyUab7aO.js} +17 -17
  128. package/dist/{gas-DlhqN9k8.js → gas-BUZOHBVw.js} +1 -1
  129. package/dist/{gherkin-0SlhEZ3t.js → gherkin-CrSjX26K.js} +1 -1
  130. package/dist/gitGraph-4MIJSDKK-Ch7kj-d2.js +2 -0
  131. package/dist/{gitGraphDiagram-WWUBYQGX-D5nSIzdE.js → gitGraphDiagram-WWUBYQGX-BPeDECwG.js} +4 -4
  132. package/dist/{glide-data-editor-E8Z7sC5t.js → glide-data-editor-BQCggnhx.js} +6619 -2875
  133. package/dist/{glide-utils-pisbYVjH.js → glide-utils-C4TKZeBA.js} +109 -78
  134. package/dist/{globals-D7sKfXY8.js → globals-DJuYoG49.js} +3 -3
  135. package/dist/{graphlib-BmY_CCV9.js → graphlib-nlwT3Wvy.js} +177 -220
  136. package/dist/{groovy-YhcTMyvX.js → groovy-IgGNjeU9.js} +1 -1
  137. package/dist/hasIn-C20JsQ2m.js +28 -0
  138. package/dist/{haskell-CodLxrtT.js → haskell-Bs1k3a9s.js} +1 -1
  139. package/dist/{haxe-CiRWNFkH.js → haxe-CQ8Yb3XF.js} +1 -1
  140. package/dist/{highlighted-body-KPVGNVTW-3Ziwfz-V.js → highlighted-body-KPVGNVTW-XbLtWlYz.js} +4 -4
  141. package/dist/{error-entries-CnqKxhU9.js → html-to-image-pPbR2cDx.js} +26259 -26556
  142. package/dist/{idl-DZgBq75I.js → idl-DjnxoAWx.js} +1 -1
  143. package/dist/info-A6RAGUB7-DCz_Egh9.js +2 -0
  144. package/dist/{infoDiagram-27XIBGKW-CRSDtRrd.js → infoDiagram-27XIBGKW-BTM_NXsn.js} +4 -4
  145. package/dist/isArrayLikeObject-B-HXCsex.js +626 -0
  146. package/dist/isEmpty-CdX7MTE1.js +14 -0
  147. package/dist/{ishikawaDiagram-5VMMS53U-DxLEUldB.js → ishikawaDiagram-5VMMS53U-K0FZrlvv.js} +5 -7
  148. package/dist/{javascript-BfNytHiM.js → javascript-BQtCoAFy.js} +2 -2
  149. package/dist/{journeyDiagram-3NMN7TZE-BaLqn-dZ.js → journeyDiagram-3NMN7TZE-D7hcb3Vx.js} +4 -4
  150. package/dist/{jsx-runtime-3BD1kT0s.js → jsx-runtime-6ZiU38At.js} +1 -1
  151. package/dist/{julia--tLIAG7q.js → julia-BfgtHJE5.js} +1 -1
  152. package/dist/{kanban-definition-UXKFOSKX-BLhvu8o4.js → kanban-definition-UXKFOSKX-ftznOyrz.js} +10 -10
  153. package/dist/{katex-BIdZGsyO.js → katex-BmPSeP5t.js} +15 -24
  154. package/dist/{linear-DWERh7eI.js → linear-BhKepphY.js} +2 -3
  155. package/dist/{livescript-CVPfYnY_.js → livescript-gIdRPKtg.js} +1 -1
  156. package/dist/{loader-BPdQhmlq.js → loader-BIlwkY0d.js} +3 -3
  157. package/dist/{lua-4Fa-T-qq.js → lua-CxFpWvnv.js} +2 -2
  158. package/dist/main.js +10728 -8440
  159. package/dist/{mathematica-CtYAy6GG.js → mathematica-QSveeEoW.js} +1 -1
  160. package/dist/{mbox-D0w9Ah1C.js → mbox-2JtaCPNT.js} +1 -1
  161. package/dist/{mermaid-Bjm6C1qN.js → mermaid-CNgH43ED.js} +170 -176
  162. package/dist/mermaid-HWGCJPDP-_lKUMvTM.js +1 -0
  163. package/dist/{mermaid-parser.core-DxuSfYwJ.js → mermaid-parser.core-DtOnl7m4.js} +19 -25
  164. package/dist/{mhchem-CdWPJK6g.js → mhchem-CjWbCsjZ.js} +1 -1
  165. package/dist/{micromark-factory-space-DGxAzmw8.js → micromark-factory-space-C5soVmaM.js} +1 -1
  166. package/dist/{mindmap-definition-YA3MSWOX-g2LwxnYQ.js → mindmap-definition-YA3MSWOX-CmzbxKfM.js} +13 -13
  167. package/dist/{mirc-De9aSAZM.js → mirc-BqD6ade4.js} +1 -1
  168. package/dist/{mllike-BbugeaY_.js → mllike-CT0hgEOO.js} +1 -1
  169. package/dist/{modelica-BjJwWw1F.js → modelica-DKKebcvq.js} +1 -1
  170. package/dist/{mscgen-DwiRH5ma.js → mscgen-BJCCsWjR.js} +1 -1
  171. package/dist/{mumps--plbDM9o.js → mumps-DVQAXZkU.js} +1 -1
  172. package/dist/{nginx-C4Df7REc.js → nginx-AP4LH2NN.js} +1 -1
  173. package/dist/{node-sql-parser-BiZOJ-FS.js → node-sql-parser-C9dndbfr.js} +7184 -7195
  174. package/dist/{now-jM8-M7IR.js → now-BKZCLE5P.js} +12 -13
  175. package/dist/{nsis-fiiOqLc3.js → nsis-Cah9xkdT.js} +1 -1
  176. package/dist/{ntriples-jV77I4k8.js → ntriples-CQoaLOV6.js} +2 -2
  177. package/dist/{number-overlay-editor-BV4UMoL2.js → number-overlay-editor-rhy7Cfzx.js} +14 -19
  178. package/dist/{objects-CzUgoMIF.js → objects-DCmMvZtn.js} +1 -3
  179. package/dist/{octave-Cztd01tF.js → octave-CeDj2bTn.js} +1 -1
  180. package/dist/{once-BtE-wKRL.js → once-NnpX_HnX.js} +1 -1
  181. package/dist/{oz-Bl8o3BDl.js → oz-BksQrg3k.js} +1 -1
  182. package/dist/packet-AYTQ26CC-DEMHgLPq.js +2 -0
  183. package/dist/{pascal-BO5JqysR.js → pascal-BKL17mpl.js} +1 -1
  184. package/dist/{pegDiagram-XKGWAZYB-CpMAt3Qs.js → pegDiagram-XKGWAZYB-DfTAWfGE.js} +5 -5
  185. package/dist/pencil-BUfnNZAE.js +10 -0
  186. package/dist/{perl-Drlh9s6m.js → perl-CacqJ-Ss.js} +1 -1
  187. package/dist/pick-DgpAbcwP.js +35 -0
  188. package/dist/pie-WAS4IAKB-DMjTEeHd.js +2 -0
  189. package/dist/{pieDiagram-E7YTZNPT-CVTthoZo.js → pieDiagram-E7YTZNPT-_kGQstZj.js} +7 -7
  190. package/dist/{pig-BWGR8wZw.js → pig-CXSGZ_K9.js} +1 -1
  191. package/dist/{powershell-DT99xlz_.js → powershell-DY_yIxBM.js} +3 -6
  192. package/dist/{process-output-DgQrilBD.js → process-output-BySO1nIs.js} +6 -6
  193. package/dist/{properties-Q-rkxld0.js → properties-Om0IDE07.js} +1 -1
  194. package/dist/{protobuf-DKyWZbs7.js → protobuf-B8nxxb4Q.js} +1 -1
  195. package/dist/{pug-42ZJUbEC.js → pug-lkUBPvVS.js} +3 -7
  196. package/dist/{puppet-B4UozHhh.js → puppet-DMkXsi4d.js} +1 -1
  197. package/dist/purify.es-DmnrIjg4.js +877 -0
  198. package/dist/{python-DREjAV__.js → python-JQVycMgr.js} +1 -1
  199. package/dist/{q-Bi63IpBO.js → q-DqE__nNB.js} +1 -1
  200. package/dist/{quadrantDiagram-AXDQQJYC-Blrm7V7_.js → quadrantDiagram-AXDQQJYC-DLJJzlPi.js} +3 -3
  201. package/dist/{r-DSwXWKMR.js → r-Cbliap7Q.js} +1 -1
  202. package/dist/radar-RG4KPBEZ-9PwdCvai.js +2 -0
  203. package/dist/railroad-74A4TZTK-8N9kF5jl.js +2 -0
  204. package/dist/railroad-abnf-HS5TGJTU-Sg6YmzlR.js +2 -0
  205. package/dist/railroad-ebnf-LZEXJU2U-CYwp2Oee.js +2 -0
  206. package/dist/railroad-peg-WCYAUIDC-BfDkAuQ3.js +2 -0
  207. package/dist/{railroadDiagram-O6MQD6OU-ao8cqqFB.js → railroadDiagram-O6MQD6OU-D41WmIff.js} +5 -5
  208. package/dist/{react-sHtZdjeP.js → react-CHbZqgBF.js} +1 -1
  209. package/dist/{react-dom-DUmH0GXv.js → react-dom-DhWGW7e0.js} +3 -3
  210. package/dist/{react-vega-BDpbvBCb.js → react-vega-DVr24mNk.js} +8514 -8597
  211. package/dist/{requirementDiagram-BXWQKSXE--J8vyfm-.js → requirementDiagram-BXWQKSXE-Bh53ZI3I.js} +13 -13
  212. package/dist/{reveal-component-CpEtzUCU.js → reveal-component-Deq9swmP.js} +1171 -1284
  213. package/dist/{rolldown-runtime-Cb76FRpK.js → rolldown-runtime-BPCzCO3I.js} +1 -1
  214. package/dist/{rpm-DPb1DeKM.js → rpm-DmT_LNpE.js} +1 -1
  215. package/dist/{ruby-U7fZA4DP.js → ruby-BHVZTtLW.js} +1 -1
  216. package/dist/{sankeyDiagram-P5KCCOFB-ELvOcnZH.js → sankeyDiagram-P5KCCOFB-Bd7wvmbF.js} +2 -2
  217. package/dist/{sas-CLk1JpTn.js → sas-CrG5N1he.js} +1 -1
  218. package/dist/{scheme-CT-2k2Tl.js → scheme-BHUnz-IB.js} +1 -1
  219. package/dist/select-XmXyen3r.js +1200 -0
  220. package/dist/{semaphore-89jsoHtS.js → semaphore-8KhQouXt.js} +4 -5
  221. package/dist/{sequenceDiagram-WJ2MYXX4-CiQbn62x.js → sequenceDiagram-WJ2MYXX4-DBX27dNm.js} +6 -6
  222. package/dist/{shell-BLO7fcsk.js → shell-C5IBmRU5.js} +1 -1
  223. package/dist/{sieve-nG3C7WNH.js → sieve-BzCnt_E6.js} +2 -2
  224. package/dist/{sizeCapture-INFHLROL-Dx5SpkQF.js → sizeCapture-INFHLROL-DBeZ20mI.js} +1 -1
  225. package/dist/{smalltalk-BROcZybA.js → smalltalk-DQFAQul3.js} +1 -1
  226. package/dist/{solr-mVltHNc5.js → solr-BiapYPCk.js} +1 -1
  227. package/dist/{sparql-Cv1Ksr4v.js → sparql-BcUKO1is.js} +1 -1
  228. package/dist/{spec-D_u2ipzF.js → spec-CSS9g9Su.js} +42 -12
  229. package/dist/{src-CA_m4ONl.js → src-D5uZaZib.js} +8 -14
  230. package/dist/{stateDiagram-D77RDMKH-DVEeGs5b.js → stateDiagram-D77RDMKH-CSYz2IZi.js} +18 -18
  231. package/dist/stateDiagram-v2-MP3YSRHH-DVLvtOqn.js +30 -0
  232. package/dist/{stex-Btf3omL8.js → stex-CQ_aCGTF.js} +1 -1
  233. package/dist/style.css +1 -1
  234. package/dist/{stylus-Dt4VvLXL.js → stylus-D4gHF5lg.js} +1 -1
  235. package/dist/{swift-DHsn_CVI.js → swift-CnvIBnbt.js} +1 -1
  236. package/dist/{swimlanes-42K2YHIH-CqkGoGqn.js → swimlanes-42K2YHIH-D59N0ktA.js} +37 -59
  237. package/dist/swimlanesDiagram-VR7AAH4N-CRgpmVSW.js +30 -0
  238. package/dist/{swiper-component-DKntyn8p.js → swiper-component-FBPvIqLf.js} +45 -78
  239. package/dist/{tcl-CV0OLBOl.js → tcl-DkOjjZYO.js} +1 -1
  240. package/dist/{textile-D320oKVM.js → textile-DgzBVSEW.js} +1 -1
  241. package/dist/{tiki-BF2aDPlB.js → tiki-MighF8A_.js} +1 -1
  242. package/dist/{time-BaMbJs4z.js → time-Cc-G3e31.js} +2 -2
  243. package/dist/{timeline-definition-24CTP7MA-DLRUNVAx.js → timeline-definition-24CTP7MA-C_xIIZDM.js} +5 -5
  244. package/dist/toDate-5qPtZ6HL.js +94 -0
  245. package/dist/toast-xEFvuGpt.js +491 -0
  246. package/dist/{toml-CaUN7UHo.js → toml-B0B7UAba.js} +1 -1
  247. package/dist/{tooltip-CtBZ5q8p.js → tooltip-BekOCQ3v.js} +326 -302
  248. package/dist/treeView-Q6P3EWNA-BQywp0E1.js +2 -0
  249. package/dist/treemap-WGGIJYW6-P2ZJnldw.js +2 -0
  250. package/dist/{troff-DQUFcClZ.js → troff-B4IRX71y.js} +1 -1
  251. package/dist/{ttcn-CQL1duJS.js → ttcn-B4tpvdJD.js} +1 -1
  252. package/dist/{ttcn-cfg-BMmEFJPu.js → ttcn-cfg-pfE-1FV8.js} +1 -1
  253. package/dist/{turtle-C3esgsRr.js → turtle-Bc97ufp6.js} +1 -1
  254. package/dist/use-restore-focus-DyrKLdSB.js +7602 -0
  255. package/dist/{use-vega-container-remeasure-Bz4PZMtt.js → use-vega-container-remeasure-DFBTESqc.js} +4 -8
  256. package/dist/{useAsyncData-BneN349z.js → useAsyncData-DMBliR6D.js} +21 -20
  257. package/dist/{useDateFormatter-DkajGwwd.js → useDateFormatter-Brx-S2sN.js} +4 -4
  258. package/dist/{useDeepCompareMemoize-Z_7R8wTt.js → useDeepCompareMemoize-CxywEb3a.js} +3 -3
  259. package/dist/useEvent-xDnF2X04.js +19 -0
  260. package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities--px_wpZL.js} +2 -2
  261. package/dist/useLifecycle-BMlFy2yY.js +112 -0
  262. package/dist/{vb-RK1eDXRl.js → vb-C5kpOY_q.js} +1 -1
  263. package/dist/{vbscript-BCOeM0de.js → vbscript-Ck0hgOg5.js} +1 -1
  264. package/dist/{vega-component-1lFfDE_8.js → vega-component-Bly2lDhz.js} +172 -177
  265. package/dist/{vega-embed-container-BgKnsDbB.js → vega-embed-container-DAT8OHp9.js} +7 -7
  266. package/dist/{vega-loader.browser-CpbZAXub.js → vega-loader.browser-Cnf7IHA8.js} +236 -239
  267. package/dist/{velocity-CZKOFbOI.js → velocity-2Doc6dgJ.js} +1 -1
  268. package/dist/{vennDiagram-4TSXK5OY-CypgoBVZ.js → vennDiagram-4TSXK5OY-DlOf0Mjs.js} +4 -4
  269. package/dist/{verilog-DqQTzVn8.js → verilog-CmZmYe4R.js} +2 -2
  270. package/dist/{vhdl-vQr2bfmi.js → vhdl-esMnUHZm.js} +1 -1
  271. package/dist/wardley-WFR3VGLG-CIBJ6O2G.js +2 -0
  272. package/dist/{wardleyDiagram-VM6X3IG4-B6xjiQ_g.js → wardleyDiagram-VM6X3IG4-zD_2guVe.js} +5 -5
  273. package/dist/{webidl-DzX7N40e.js → webidl-G_RlXG7G.js} +1 -1
  274. package/dist/{xquery-Cw_Q_hGR.js → xquery-IUQpMoMl.js} +1 -1
  275. package/dist/{xychartDiagram-S5SC5T6Z-e0PefDVs.js → xychartDiagram-S5SC5T6Z-CA0Dy1ZM.js} +8 -8
  276. package/dist/{yacas-DiC4jJBE.js → yacas-ObL0t2h9.js} +1 -1
  277. package/dist/{z80-CUOQDEJO.js → z80-jyuJhy0k.js} +1 -1
  278. package/package.json +16 -21
  279. package/src/__mocks__/document.tsx +63 -0
  280. package/src/__mocks__/executions.ts +219 -0
  281. package/src/__mocks__/notebook.ts +3 -7
  282. package/src/__mocks__/partial.ts +26 -0
  283. package/src/__mocks__/requests.ts +1 -5
  284. package/src/__tests__/CellStatus.test.tsx +25 -26
  285. package/src/__tests__/fixtures/pair-config.ts +1 -1
  286. package/src/__tests__/main.test.tsx +24 -1
  287. package/src/__tests__/module-entry.test.ts +44 -0
  288. package/src/__tests__/mount.test.ts +1 -21
  289. package/src/__tests__/{host-imports.test.ts → replica-imports.test.ts} +12 -11
  290. package/src/components/ai/__tests__/ai-model-dropdown.test.tsx +63 -0
  291. package/src/components/ai/__tests__/ai-utils.test.ts +8 -0
  292. package/src/components/ai/ai-model-dropdown.tsx +11 -6
  293. package/src/components/app-config/__tests__/state.test.tsx +50 -0
  294. package/src/components/app-config/ai-config.tsx +154 -150
  295. package/src/components/app-config/app-config-button.tsx +6 -5
  296. package/src/components/app-config/app-config-form.tsx +5 -9
  297. package/src/components/app-config/common.tsx +7 -9
  298. package/src/components/app-config/data-form.tsx +113 -116
  299. package/src/components/app-config/get-dirty-values.ts +4 -4
  300. package/src/components/app-config/mcp-config.tsx +30 -53
  301. package/src/components/app-config/user-config-form.css +91 -0
  302. package/src/components/app-config/user-config-form.tsx +266 -349
  303. package/src/components/chat/__tests__/capabilities-popover.test.tsx +0 -1
  304. package/src/components/chat/__tests__/chat-display.test.tsx +196 -0
  305. package/src/components/chat/__tests__/chat-markdown.test.tsx +43 -0
  306. package/src/components/chat/__tests__/chat-utils.test.ts +93 -1
  307. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  308. package/src/components/chat/__tests__/notebook-access-selector.test.tsx +64 -0
  309. package/src/components/chat/__tests__/turn-timing.test.ts +84 -0
  310. package/src/components/chat/__tests__/use-assistant-turn.test.ts +95 -0
  311. package/src/components/chat/__tests__/user-message.test.tsx +27 -0
  312. package/src/components/chat/acp/__tests__/agent-panel-initialization.test.tsx +1 -1
  313. package/src/components/chat/acp/__tests__/agent-panel.test.tsx +22 -17
  314. package/src/components/chat/acp/__tests__/session-tabs.test.tsx +43 -0
  315. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  316. package/src/components/chat/acp/agent-panel.tsx +6 -3
  317. package/src/components/chat/acp/blocks.tsx +13 -12
  318. package/src/components/chat/acp/common.tsx +4 -5
  319. package/src/components/chat/acp/model-selector.tsx +1 -1
  320. package/src/components/chat/acp/session-tabs.tsx +8 -3
  321. package/src/components/chat/agent-activity.tsx +132 -0
  322. package/src/components/chat/capabilities-popover.tsx +2 -11
  323. package/src/components/chat/chat-components.tsx +9 -4
  324. package/src/components/chat/chat-display.tsx +131 -57
  325. package/src/components/chat/chat-history-popover.tsx +6 -1
  326. package/src/components/chat/chat-markdown.tsx +15 -0
  327. package/src/components/chat/chat-panel.tsx +216 -294
  328. package/src/components/chat/chat-typography.css +108 -0
  329. package/src/components/chat/chat-utils.ts +24 -35
  330. package/src/components/chat/notebook-access-selector.stories.tsx +22 -0
  331. package/src/components/chat/notebook-access-selector.tsx +100 -0
  332. package/src/components/chat/tool-call/__tests__/tool-labels.test.ts +76 -0
  333. package/src/components/chat/tool-call/__tests__/tool-step.test.tsx +57 -0
  334. package/src/components/chat/tool-call/shared.ts +6 -0
  335. package/src/components/chat/tool-call/tool-call-view.tsx +22 -76
  336. package/src/components/chat/tool-call/tool-labels.ts +172 -0
  337. package/src/components/chat/tool-call/tool-result.tsx +1 -1
  338. package/src/components/chat/tool-call/tool-step.tsx +145 -0
  339. package/src/components/chat/turn-timing.ts +73 -0
  340. package/src/components/chat/use-assistant-turn.ts +65 -0
  341. package/src/components/chat/use-chat-generation.ts +20 -0
  342. package/src/components/chat/user-message.tsx +145 -0
  343. package/src/components/data-table/TableBottomBar.tsx +4 -1
  344. package/src/components/data-table/TableTopBar.tsx +2 -0
  345. package/src/components/data-table/__tests__/chart-spec-model.test.ts +195 -195
  346. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  347. package/src/components/data-table/__tests__/columns.test.tsx +105 -0
  348. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  349. package/src/components/data-table/__tests__/data-table.test.tsx +1 -0
  350. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  351. package/src/components/data-table/__tests__/export-actions.test.tsx +105 -0
  352. package/src/components/data-table/__tests__/keyboard-selection.test.tsx +188 -0
  353. package/src/components/data-table/__tests__/pagination.test.tsx +28 -1
  354. package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
  355. package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
  356. package/src/components/data-table/cell-selection/feature.ts +2 -2
  357. package/src/components/data-table/cell-selection/types.ts +2 -2
  358. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
  359. package/src/components/data-table/charts/__tests__/spec-snapshot.test.ts +20 -20
  360. package/src/components/data-table/charts/__tests__/spec.test.ts +78 -52
  361. package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
  362. package/src/components/data-table/charts/chart-spec/spec.ts +68 -43
  363. package/src/components/data-table/charts/charts.tsx +40 -30
  364. package/src/components/data-table/charts/storage.ts +16 -5
  365. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  366. package/src/components/data-table/column-formatting/types.ts +1 -2
  367. package/src/components/data-table/column-summary/chart-spec-model.tsx +23 -16
  368. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  369. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  370. package/src/components/data-table/columns.tsx +45 -46
  371. package/src/components/data-table/context-menu.tsx +36 -20
  372. package/src/components/data-table/data-table.tsx +17 -1
  373. package/src/components/data-table/date-popover.tsx +6 -4
  374. package/src/components/data-table/export-actions.tsx +76 -48
  375. package/src/components/data-table/pagination.tsx +44 -33
  376. package/src/components/data-table/range-focus/atoms.ts +4 -6
  377. package/src/components/data-table/range-focus/use-cell-range-selection.ts +65 -38
  378. package/src/components/data-table/range-focus/use-scroll-into-view.ts +21 -2
  379. package/src/components/data-table/renderers.tsx +16 -7
  380. package/src/components/data-table/row-viewer-panel/__tests__/row-viewer.test.tsx +24 -1
  381. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +4 -4
  382. package/src/components/data-table/search-highlight.tsx +79 -0
  383. package/src/components/data-table/types.ts +1 -0
  384. package/src/components/data-table/url-detector.tsx +49 -9
  385. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  386. package/src/components/datasources/__tests__/column-preview.test.tsx +96 -5
  387. package/src/components/datasources/__tests__/datasources-interactions.test.tsx +181 -0
  388. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  389. package/src/components/datasources/__tests__/utils.test.ts +57 -6
  390. package/src/components/datasources/column-preview.tsx +17 -12
  391. package/src/components/datasources/datasources.tsx +56 -43
  392. package/src/components/datasources/utils.ts +32 -23
  393. package/src/components/debugger/debugger-code.tsx +5 -0
  394. package/src/components/dependency-graph/custom-node.tsx +1 -1
  395. package/src/components/dependency-graph/dependency-graph-tree.tsx +8 -8
  396. package/src/components/dependency-graph/elements.ts +24 -21
  397. package/src/components/dependency-graph/minimap-content.tsx +15 -20
  398. package/src/components/dependency-graph/panels.tsx +4 -2
  399. package/src/components/dependency-graph/utils/__tests__/layout.test.ts +18 -11
  400. package/src/components/editor/SortableCell.tsx +1 -3
  401. package/src/components/editor/__tests__/Output.test.tsx +50 -32
  402. package/src/{core/attachments/__tests__/toast.test.tsx → components/editor/__tests__/agent-toast.test.tsx} +1 -1
  403. package/src/components/editor/__tests__/data-attributes.test.tsx +38 -26
  404. package/src/components/editor/__tests__/output-persistence.test.tsx +28 -9
  405. package/src/components/editor/__tests__/package-alert.test.tsx +22 -1
  406. package/src/components/editor/actions/__tests__/name-cell-input.test.tsx +69 -0
  407. package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +21 -40
  408. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +41 -14
  409. package/src/components/editor/actions/__tests__/pair-with-agent.test.tsx +6 -24
  410. package/src/components/editor/actions/__tests__/useCellActionButton.test.tsx +131 -0
  411. package/src/components/editor/actions/__tests__/useSetOutputsExpanded.test.ts +2 -1
  412. package/src/components/editor/actions/name-cell-input.tsx +4 -3
  413. package/src/components/editor/actions/pair-with-agent-commands.ts +10 -7
  414. package/src/components/editor/actions/pair-with-agent-modal.tsx +29 -30
  415. package/src/components/editor/actions/pair-with-agent.tsx +16 -39
  416. package/src/components/editor/actions/useCellActionButton.tsx +47 -35
  417. package/src/components/editor/actions/useConfigActions.tsx +3 -4
  418. package/src/components/editor/actions/useNotebookActions.tsx +25 -29
  419. package/src/components/editor/actions/useRestartKernel.tsx +1 -1
  420. package/src/components/editor/actions/useSetCodeVisibility.ts +11 -12
  421. package/src/components/editor/actions/useSetOutputsExpanded.ts +15 -23
  422. package/src/components/editor/ai/ai-completion-editor.tsx +2 -1
  423. package/src/components/editor/ai/completion-handlers.tsx +0 -37
  424. package/src/components/editor/ai/prompt-input.tsx +189 -0
  425. package/src/components/editor/alerts/__tests__/connection-notice.test.tsx +1 -1
  426. package/src/components/editor/alerts/connection-notice.tsx +1 -1
  427. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  428. package/src/components/editor/alerts/stdin-blocking-alert.tsx +4 -2
  429. package/src/components/editor/app-container.tsx +1 -1
  430. package/src/components/editor/boundary/__tests__/ErrorBoundary.test.tsx +1 -1
  431. package/src/components/editor/cell/CellStatus.tsx +28 -23
  432. package/src/components/editor/cell/CreateCellButton.tsx +15 -21
  433. package/src/components/editor/cell/DeleteButton.tsx +3 -2
  434. package/src/components/editor/cell/RunButton.tsx +18 -8
  435. package/src/components/editor/cell/StagedAICell.tsx +52 -137
  436. package/src/components/editor/cell/StopButton.tsx +2 -2
  437. package/src/components/editor/cell/__tests__/useRunCells.test.tsx +154 -0
  438. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  439. package/src/components/editor/cell/cell-actions.tsx +12 -1
  440. package/src/components/editor/cell/cell-context-menu.tsx +4 -1
  441. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  442. package/src/components/editor/cell/code/cell-editor.tsx +65 -169
  443. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  444. package/src/components/editor/cell/collapse.tsx +16 -6
  445. package/src/components/editor/cell/useAddCell.ts +4 -8
  446. package/src/components/editor/cell/useDeleteCell.tsx +10 -36
  447. package/src/components/editor/cell/useRunCells.ts +72 -58
  448. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  449. package/src/components/editor/cell/useSplitCell.tsx +10 -6
  450. package/src/components/editor/chrome/components/__tests__/feedback-button.test.tsx +1 -1
  451. package/src/components/editor/chrome/components/feedback-button.tsx +2 -8
  452. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +31 -2
  453. package/src/components/editor/chrome/panels/__tests__/runtime-details.test.tsx +1 -1
  454. package/src/components/editor/chrome/panels/__tests__/sandbox-recovery.test.tsx +1 -1
  455. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  456. package/src/components/editor/chrome/panels/dependency-graph-panel.tsx +1 -1
  457. package/src/components/editor/chrome/panels/logs-panel.tsx +2 -1
  458. package/src/components/editor/chrome/panels/outline/__tests__/outline-keyboard.test.tsx +84 -0
  459. package/src/components/editor/chrome/panels/outline/floating-outline.tsx +39 -14
  460. package/src/components/editor/chrome/panels/outline/useActiveOutline.tsx +3 -2
  461. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  462. package/src/components/editor/chrome/panels/panel-header.tsx +13 -0
  463. package/src/components/editor/chrome/panels/secrets-panel.tsx +1 -0
  464. package/src/components/editor/chrome/panels/session-panel.tsx +1 -1
  465. package/src/components/editor/chrome/panels/snippets-panel.tsx +10 -12
  466. package/src/components/editor/chrome/panels/variable-panel.tsx +1 -5
  467. package/src/components/editor/chrome/types.ts +1 -1
  468. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +7 -9
  469. package/src/components/editor/chrome/wrapper/__tests__/document-sync-status.test.tsx +92 -0
  470. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +265 -37
  471. package/src/components/editor/chrome/wrapper/__tests__/useAskAiAboutCell.test.tsx +58 -0
  472. package/src/components/editor/chrome/wrapper/app-chrome.tsx +91 -74
  473. package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +4 -4
  474. package/src/components/editor/chrome/wrapper/footer-items/backend-status.tsx +1 -1
  475. package/src/components/editor/chrome/wrapper/footer-items/document-sync-status.tsx +54 -0
  476. package/src/components/editor/chrome/wrapper/footer-items/lsp-status.tsx +1 -1
  477. package/src/components/editor/chrome/wrapper/footer-items/machine-stats.tsx +1 -1
  478. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  479. package/src/components/editor/chrome/wrapper/footer.tsx +2 -2
  480. package/src/components/editor/chrome/wrapper/minimap-state.ts +1 -1
  481. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +143 -72
  482. package/src/components/editor/chrome/wrapper/pending-ai-changes-badge.tsx +25 -0
  483. package/src/components/editor/chrome/wrapper/sidebar.tsx +8 -4
  484. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  485. package/src/components/editor/chrome/wrapper/useAskAiAboutCell.ts +36 -0
  486. package/src/components/editor/columns/__tests__/cell-column.test.tsx +1 -1
  487. package/src/components/editor/columns/cell-column.tsx +1 -1
  488. package/src/components/editor/columns/sortable-column.tsx +2 -2
  489. package/src/components/editor/connections/components.tsx +5 -10
  490. package/src/components/editor/controls/Controls.tsx +4 -4
  491. package/src/components/editor/controls/__tests__/keyboard-shortcuts.test.tsx +61 -0
  492. package/src/components/editor/controls/__tests__/notebook-menu-dropdown.test.tsx +10 -4
  493. package/src/components/editor/controls/keyboard-shortcuts.tsx +14 -5
  494. package/src/components/editor/documentation.css +34 -11
  495. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +26 -22
  496. package/src/components/editor/errors/auto-fix.tsx +4 -8
  497. package/src/components/editor/errors/send-error-report-button.tsx +3 -6
  498. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  499. package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +47 -0
  500. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  501. package/src/components/editor/file-tree/file-explorer.tsx +48 -15
  502. package/src/components/editor/file-tree/file-name-input.tsx +1 -0
  503. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  504. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  505. package/src/components/editor/header/__tests__/filename-form.test.tsx +1 -4
  506. package/src/components/editor/header/__tests__/status.test.tsx +5 -3
  507. package/src/components/editor/header/app-header.tsx +1 -1
  508. package/src/components/editor/header/filename-form.tsx +1 -1
  509. package/src/components/editor/header/status.tsx +44 -22
  510. package/src/components/editor/links/__tests__/cell-link.test.tsx +148 -0
  511. package/src/components/editor/links/cell-link-list.tsx +1 -1
  512. package/src/components/editor/links/cell-link.tsx +21 -11
  513. package/src/components/editor/navigation/__tests__/clipboard.test.ts +60 -38
  514. package/src/components/editor/navigation/__tests__/navigation.test.ts +109 -85
  515. package/src/components/editor/navigation/__tests__/selection.test.ts +43 -21
  516. package/src/components/editor/navigation/clipboard.ts +17 -14
  517. package/src/components/editor/navigation/focus-utils.ts +23 -4
  518. package/src/components/editor/navigation/multi-cell-action-toolbar.tsx +17 -33
  519. package/src/components/editor/navigation/navigation.ts +74 -72
  520. package/src/components/editor/navigation/selection.ts +30 -29
  521. package/src/components/editor/notebook-banner.tsx +1 -0
  522. package/src/components/editor/notebook-cell.tsx +158 -172
  523. package/src/components/editor/output/MarimoErrorOutput.tsx +41 -4
  524. package/src/components/editor/output/MarimoTracebackOutput.tsx +19 -17
  525. package/src/components/editor/output/Outputs.css +8 -0
  526. package/src/components/editor/output/VideoOutput.tsx +6 -0
  527. package/src/components/editor/output/__tests__/VideoOutput.test.tsx +131 -0
  528. package/src/components/editor/output/__tests__/traceback.test.tsx +27 -1
  529. package/src/components/editor/output/ansi-reduce.ts +40 -20
  530. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  531. package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +125 -69
  532. package/src/components/editor/package-alert.tsx +14 -7
  533. package/src/components/editor/renderers/__tests__/add-cell-buttons.test.tsx +18 -0
  534. package/src/components/editor/renderers/cell-array.tsx +32 -100
  535. package/src/components/editor/renderers/grid-layout/__tests__/grid-layout.test.tsx +75 -0
  536. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +28 -22
  537. package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +0 -4
  538. package/src/components/editor/renderers/slides-layout/slides-layout.tsx +1 -1
  539. package/src/components/editor/renderers/vertical-layout/__tests__/useFocusFirstEditor.test.ts +4 -4
  540. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  541. package/src/components/editor/renderers/vertical-layout/useFocusFirstEditor.ts +1 -1
  542. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +19 -37
  543. package/src/components/editor/stdin-blocking-alert.tsx +4 -2
  544. package/src/components/home/components.tsx +17 -31
  545. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  546. package/src/components/icons/large-spinner.tsx +3 -14
  547. package/src/components/icons/spinner.tsx +1 -1
  548. package/src/components/markdown/__tests__/markdown-renderer.test.tsx +32 -2
  549. package/src/components/markdown/markdown-renderer.tsx +43 -28
  550. package/src/components/mcp/mcp-status-indicator.tsx +18 -14
  551. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  552. package/src/components/pages/edit-page.tsx +19 -17
  553. package/src/components/pages/home-page.tsx +164 -91
  554. package/src/components/scratchpad/scratchpad.test.tsx +89 -0
  555. package/src/components/scratchpad/scratchpad.tsx +47 -33
  556. package/src/components/scratchpad/state.ts +7 -0
  557. package/src/components/slides/__tests__/minimap-actions.test.tsx +22 -29
  558. package/src/components/slides/__tests__/minimap.test.ts +38 -19
  559. package/src/components/slides/__tests__/slide-scroll-hint.test.tsx +37 -0
  560. package/src/components/slides/minimap.tsx +22 -14
  561. package/src/components/slides/reveal-component.tsx +73 -36
  562. package/src/components/slides/reveal-slides.css +5 -4
  563. package/src/components/slides/slide-cell-view.tsx +37 -34
  564. package/src/components/slides/slide-form.tsx +1 -0
  565. package/src/components/slides/slide-scroll-hint.tsx +3 -1
  566. package/src/components/slides/slide.tsx +3 -4
  567. package/src/components/sort/SortableCellsProvider.tsx +8 -25
  568. package/src/components/static-html/__tests__/static-banner.test.tsx +1 -1
  569. package/src/components/tracing/tracing-keyboard.test.tsx +85 -0
  570. package/src/components/tracing/tracing.tsx +46 -42
  571. package/src/components/ui/__tests__/draggable-popover.test.tsx +24 -0
  572. package/src/components/ui/__tests__/fullscreen.test.tsx +44 -2
  573. package/src/components/ui/__tests__/range-slider.test.tsx +165 -0
  574. package/src/components/ui/alert-dialog.tsx +1 -1
  575. package/src/components/ui/draggable-popover.tsx +24 -2
  576. package/src/components/ui/fullscreen.tsx +25 -1
  577. package/src/components/ui/range-slider.tsx +22 -9
  578. package/src/components/ui/toast.tsx +1 -1
  579. package/src/core/MarimoApp.tsx +3 -1
  580. package/src/core/ai/__tests__/staged-cells.test.ts +90 -609
  581. package/src/core/ai/context/context.ts +2 -0
  582. package/src/core/ai/context/providers/__tests__/cell-output.test.ts +8 -12
  583. package/src/core/ai/context/providers/__tests__/cell.test.ts +49 -0
  584. package/src/core/ai/context/providers/cell-output.ts +2 -2
  585. package/src/core/ai/context/providers/cell.ts +54 -0
  586. package/src/core/ai/context/providers/datasource.ts +6 -2
  587. package/src/core/ai/staged-cells.ts +56 -323
  588. package/src/core/ai/state.ts +3 -0
  589. package/src/core/ai/tools/__tests__/edit-notebook-tool.test.ts +47 -91
  590. package/src/core/ai/tools/__tests__/registry.test.ts +17 -0
  591. package/src/core/ai/tools/base.ts +18 -15
  592. package/src/core/ai/tools/edit-notebook-tool.ts +86 -80
  593. package/src/core/ai/tools/registry.ts +13 -10
  594. package/src/core/ai/tools/sample-tool.ts +6 -7
  595. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +6 -529
  596. package/src/core/cells/__tests__/actions.test.ts +623 -35
  597. package/src/core/cells/__tests__/add-missing-import.test.ts +7 -8
  598. package/src/core/cells/__tests__/arrange.test.ts +432 -0
  599. package/src/core/cells/__tests__/cell.test.ts +148 -150
  600. package/src/core/cells/__tests__/cells.test.ts +384 -2930
  601. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +69 -0
  602. package/src/core/cells/__tests__/document.test.ts +158 -0
  603. package/src/core/cells/__tests__/editor.test.ts +57 -0
  604. package/src/core/cells/__tests__/flush-document.test.ts +40 -0
  605. package/src/core/cells/__tests__/harness.ts +109 -0
  606. package/src/core/cells/__tests__/layout.test.ts +176 -0
  607. package/src/core/cells/__tests__/logs.test.ts +0 -13
  608. package/src/core/cells/__tests__/notebook-outline.test.ts +23 -14
  609. package/src/core/cells/__tests__/outputs.test.tsx +71 -53
  610. package/src/core/cells/__tests__/pending-delete-service.test.tsx +11 -20
  611. package/src/core/cells/__tests__/session.test.ts +546 -211
  612. package/src/core/cells/__tests__/utils.test.ts +135 -337
  613. package/src/core/cells/actions.ts +677 -6
  614. package/src/core/cells/add-missing-import.ts +48 -47
  615. package/src/core/cells/arrange.ts +529 -0
  616. package/src/core/cells/cell.ts +79 -146
  617. package/src/core/cells/cells.ts +106 -1459
  618. package/src/core/cells/columns.ts +38 -0
  619. package/src/core/cells/document.ts +167 -0
  620. package/src/core/cells/edit.ts +30 -0
  621. package/src/core/cells/editor.ts +82 -0
  622. package/src/core/cells/focus.ts +27 -6
  623. package/src/core/cells/layout.ts +221 -0
  624. package/src/core/cells/outputs.ts +12 -28
  625. package/src/core/cells/pending-delete-service.ts +8 -14
  626. package/src/core/cells/scrollCellIntoView.ts +6 -0
  627. package/src/core/cells/session.ts +166 -68
  628. package/src/core/cells/types.ts +21 -51
  629. package/src/core/cells/utils.ts +85 -87
  630. package/src/core/codemirror/__tests__/format.test.ts +4 -35
  631. package/src/core/codemirror/__tests__/setup.test.ts +67 -33
  632. package/src/core/codemirror/ai/__tests__/resources.test.ts +117 -0
  633. package/src/core/codemirror/ai/resources.ts +1 -1
  634. package/src/core/codemirror/cells/__tests__/extensions.test.ts +2 -6
  635. package/src/core/codemirror/cells/extensions.ts +18 -30
  636. package/src/core/codemirror/cells/state.ts +2 -6
  637. package/src/core/codemirror/cm.ts +5 -1
  638. package/src/core/codemirror/config/types.ts +1 -1
  639. package/src/core/codemirror/copilot/__tests__/completion-integration.test.ts +4 -0
  640. package/src/core/codemirror/copilot/__tests__/getCodes.test.ts +13 -10
  641. package/src/core/codemirror/copilot/getCodes.ts +2 -2
  642. package/src/core/codemirror/format.ts +6 -23
  643. package/src/core/codemirror/keymaps/vim.ts +4 -8
  644. package/src/core/codemirror/language/__tests__/extension.test.ts +7 -3
  645. package/src/core/codemirror/language/__tests__/sql.test.ts +22 -20
  646. package/src/core/codemirror/language/__tests__/utils.test.ts +194 -17
  647. package/src/core/codemirror/language/cell-code.ts +114 -0
  648. package/src/core/codemirror/language/extension.ts +52 -35
  649. package/src/core/codemirror/language/languages/markdown.ts +8 -6
  650. package/src/core/codemirror/language/languages/python.ts +19 -13
  651. package/src/core/codemirror/language/languages/sql/sql.ts +16 -24
  652. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  653. package/src/core/codemirror/language/types.ts +7 -7
  654. package/src/core/codemirror/language/utils.ts +9 -39
  655. package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +1 -2
  656. package/src/core/codemirror/lsp/notebook-lsp.ts +6 -5
  657. package/src/core/codemirror/lsp/utils.ts +1 -1
  658. package/src/core/codemirror/misc/__tests__/paste.test.ts +3 -1
  659. package/src/core/codemirror/misc/paste.ts +1 -1
  660. package/src/core/codemirror/placeholder/__tests__/extensions.test.ts +75 -0
  661. package/src/core/codemirror/placeholder/extensions.ts +80 -19
  662. package/src/core/codemirror/readonly/__tests__/extension.test.ts +1 -1
  663. package/src/core/codemirror/readonly/extension.ts +2 -2
  664. package/src/core/codemirror/utils.ts +1 -0
  665. package/src/core/config/__tests__/app-config.test.ts +64 -0
  666. package/src/core/config/__tests__/config-schema.test.ts +177 -17
  667. package/src/core/config/config-schema.ts +116 -18
  668. package/src/core/config/config.ts +65 -20
  669. package/src/core/config/feature-flag.tsx +0 -2
  670. package/src/core/datasets/__tests__/data-source-discovery.test.ts +5 -5
  671. package/src/core/datasets/data-source-discovery.ts +4 -6
  672. package/src/core/documentation/doc-lookup.ts +1 -1
  673. package/src/core/dom/outline.ts +4 -1
  674. package/src/core/edit-app.tsx +22 -28
  675. package/src/core/errors/__tests__/error-entries.test.ts +40 -1
  676. package/src/core/errors/error-entries.ts +6 -4
  677. package/src/core/executions/__tests__/atoms.test.ts +282 -0
  678. package/src/core/executions/__tests__/freshness.test.ts +482 -0
  679. package/src/core/executions/__tests__/hooks.test.tsx +150 -0
  680. package/src/core/executions/__tests__/input.test.ts +86 -0
  681. package/src/core/executions/__tests__/output.test.ts +266 -0
  682. package/src/core/executions/__tests__/run.test.ts +390 -0
  683. package/src/core/executions/__tests__/select.test.ts +343 -0
  684. package/src/core/executions/atoms.ts +378 -0
  685. package/src/core/executions/freshness.ts +171 -0
  686. package/src/core/executions/hooks.ts +86 -0
  687. package/src/core/executions/index.ts +49 -0
  688. package/src/core/executions/input.ts +55 -0
  689. package/src/core/executions/output.ts +242 -0
  690. package/src/core/executions/run.ts +120 -0
  691. package/src/core/executions/select.ts +210 -0
  692. package/src/core/export/__tests__/hooks.test.ts +0 -5
  693. package/src/core/export/hooks.ts +1 -1
  694. package/src/core/export/layout.ts +2 -2
  695. package/src/core/hotkeys/actions.ts +2 -4
  696. package/src/core/hotkeys/hotkeys.ts +2 -1
  697. package/src/core/islands/__tests__/bootstrap.test.ts +69 -17
  698. package/src/core/islands/__tests__/bridge.test.ts +104 -65
  699. package/src/core/islands/bootstrap.ts +53 -54
  700. package/src/core/islands/bridge.ts +88 -41
  701. package/src/core/islands/components/IslandControls.tsx +15 -7
  702. package/src/core/islands/components/__tests__/IslandControls.test.tsx +36 -51
  703. package/src/core/islands/components/__tests__/web-components.test.tsx +63 -0
  704. package/src/core/islands/components/output-wrapper.tsx +5 -5
  705. package/src/core/islands/components/web-components.tsx +12 -1
  706. package/src/core/islands/worker/__tests__/controller.test.ts +62 -4
  707. package/src/core/islands/worker/controller.ts +4 -10
  708. package/src/core/islands/worker/worker.tsx +78 -52
  709. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  710. package/src/core/kernel/__tests__/handlers.test.ts +7 -157
  711. package/src/core/kernel/handlers.ts +13 -79
  712. package/src/core/kernel/state.ts +0 -15
  713. package/src/core/lifecycle/render-policy.ts +12 -26
  714. package/src/core/meta/__tests__/code-visibility.test.tsx +78 -70
  715. package/src/core/meta/code-visibility.ts +18 -9
  716. package/src/core/network/__tests__/connection.test.ts +1 -1
  717. package/src/core/network/__tests__/requests-lazy.test.ts +20 -32
  718. package/src/core/network/__tests__/requests-toasting.test.tsx +7 -7
  719. package/src/core/network/connection-notice.ts +1 -1
  720. package/src/core/network/connection.ts +1 -1
  721. package/src/core/network/requests-lazy.ts +5 -9
  722. package/src/core/network/requests-network.ts +5 -40
  723. package/src/core/network/requests-static.ts +1 -5
  724. package/src/core/network/requests-toasting.tsx +1 -5
  725. package/src/core/network/types.ts +6 -16
  726. package/src/core/network/useConnectionNotice.ts +8 -2
  727. package/src/core/notebook/__tests__/operations.test.ts +51 -0
  728. package/src/core/notebook/attachments.ts +38 -0
  729. package/src/core/notebook/document/__tests__/codemirror.test.ts +765 -0
  730. package/src/core/notebook/document/__tests__/draft.test.ts +363 -0
  731. package/src/core/notebook/document/__tests__/fixtures.ts +92 -0
  732. package/src/core/notebook/document/__tests__/hooks.test.tsx +391 -0
  733. package/src/core/notebook/document/__tests__/lifecycle-scenarios.test.ts +61 -0
  734. package/src/core/notebook/document/__tests__/model.test.ts +247 -0
  735. package/src/core/notebook/document/__tests__/protocol-fixtures.ts +38 -0
  736. package/src/core/notebook/document/__tests__/recovery-scenarios.test.ts +230 -0
  737. package/src/core/notebook/document/__tests__/scenario-fixture.ts +43 -0
  738. package/src/core/notebook/document/__tests__/scenarios.test.ts +205 -0
  739. package/src/core/notebook/document/__tests__/state.test.ts +459 -0
  740. package/src/core/notebook/document/__tests__/submission.test.ts +203 -0
  741. package/src/core/notebook/document/__tests__/test-host.test.ts +92 -0
  742. package/src/core/notebook/document/__tests__/test-host.ts +247 -0
  743. package/src/core/notebook/document/__tests__/test-notebook.ts +149 -0
  744. package/src/core/notebook/document/__tests__/user.ts +61 -0
  745. package/src/core/notebook/document/codemirror.ts +216 -0
  746. package/src/core/notebook/document/draft.ts +502 -0
  747. package/src/core/notebook/document/hooks.tsx +118 -0
  748. package/src/core/notebook/document/index.ts +24 -0
  749. package/src/core/notebook/document/model.ts +102 -0
  750. package/src/core/notebook/document/select.ts +41 -0
  751. package/src/core/notebook/document/state.ts +378 -0
  752. package/src/core/notebook/document/sync.ts +369 -0
  753. package/src/core/notebook/index.ts +19 -0
  754. package/src/core/notebook/operations.ts +30 -0
  755. package/src/core/notebook/replica/AGENTS.md +11 -0
  756. package/src/core/{host → notebook/replica}/__tests__/attachments.test.ts +9 -10
  757. package/src/core/{host → notebook/replica}/__tests__/document.test.ts +18 -16
  758. package/src/core/notebook/replica/__tests__/executions.test.ts +200 -0
  759. package/src/core/notebook/replica/__tests__/fixtures.ts +95 -0
  760. package/src/core/notebook/replica/__tests__/replica.test.ts +559 -0
  761. package/src/core/notebook/replica/__tests__/results.test.ts +193 -0
  762. package/src/core/notebook/replica/__tests__/runtime.test.ts +78 -0
  763. package/src/core/notebook/replica/backend.ts +21 -0
  764. package/src/core/notebook/replica/connection.ts +42 -0
  765. package/src/core/notebook/replica/index.ts +20 -0
  766. package/src/core/notebook/replica/replica.ts +336 -0
  767. package/src/core/{host → notebook/replica}/resources/attachments.ts +6 -3
  768. package/src/core/{host → notebook/replica}/resources/document.ts +3 -3
  769. package/src/core/notebook/replica/resources/executions.ts +157 -0
  770. package/src/core/notebook/replica/resources/results.ts +48 -0
  771. package/src/core/notebook/replica/resources/runtime.ts +14 -0
  772. package/src/core/packages/__tests__/useSandboxController.test.tsx +1 -1
  773. package/src/core/packages/useSandboxController.ts +1 -1
  774. package/src/core/run-app.tsx +49 -51
  775. package/src/core/runtime/__tests__/adapter.test.ts +4 -4
  776. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  777. package/src/core/runtime/__tests__/runtime.test.ts +68 -74
  778. package/src/core/runtime/adapter.ts +5 -5
  779. package/src/core/runtime/config.ts +38 -3
  780. package/src/core/runtime/runtime.ts +54 -47
  781. package/src/core/runtime/types.ts +0 -2
  782. package/src/core/saving/__tests__/document.test.tsx +123 -0
  783. package/src/core/saving/__tests__/state.test.ts +111 -0
  784. package/src/core/saving/__tests__/useAutoSave.test.tsx +67 -0
  785. package/src/core/saving/document.ts +33 -0
  786. package/src/core/saving/filename.ts +1 -1
  787. package/src/core/saving/save-component.tsx +39 -60
  788. package/src/core/saving/state.ts +38 -39
  789. package/src/core/saving/useAutoSave.ts +17 -53
  790. package/src/core/session/__tests__/backend.test.ts +236 -0
  791. package/src/core/session/__tests__/useNotebookReplica.hook.test.tsx +186 -0
  792. package/src/core/{websocket → session}/__tests__/useNotebookSession.hook.test.tsx +245 -262
  793. package/src/core/session/active-notebook.ts +28 -0
  794. package/src/core/session/backend.ts +123 -0
  795. package/src/core/session/embedded.ts +7 -0
  796. package/src/core/{websocket → session}/types.ts +2 -2
  797. package/src/core/session/useNotebookReplica.ts +74 -0
  798. package/src/core/{websocket → session}/useNotebookSession.tsx +80 -67
  799. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  800. package/src/core/state/jotai.ts +1 -1
  801. package/src/core/static/__tests__/files.test.ts +38 -10
  802. package/src/core/static/files.ts +2 -0
  803. package/src/core/{websocket → transports}/__tests__/connection-state.test.ts +1 -13
  804. package/src/core/transports/__tests__/server.test.ts +408 -0
  805. package/src/core/transports/__tests__/static.test.ts +340 -0
  806. package/src/core/transports/__tests__/wasm.test.ts +166 -0
  807. package/src/core/{websocket → transports}/connection-state.ts +9 -16
  808. package/src/core/transports/server.ts +152 -0
  809. package/src/core/transports/static.ts +174 -0
  810. package/src/core/transports/wasm.ts +97 -0
  811. package/src/core/vscode/__tests__/vscode-bindings.test.ts +125 -0
  812. package/src/core/vscode/vscode-bindings.ts +14 -2
  813. package/src/core/wasm/__tests__/bridge.test.ts +210 -64
  814. package/src/core/wasm/__tests__/worker.test.ts +191 -0
  815. package/src/core/wasm/bridge.ts +111 -81
  816. package/src/core/wasm/rpc.ts +15 -3
  817. package/src/core/wasm/store.ts +3 -3
  818. package/src/core/wasm/worker/bootstrap.ts +55 -51
  819. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  820. package/src/core/wasm/worker/save-worker.ts +9 -10
  821. package/src/core/wasm/worker/types.ts +31 -18
  822. package/src/core/wasm/worker/worker.ts +120 -72
  823. package/src/css/app/Cell.css +7 -36
  824. package/src/css/app/codemirror.css +1 -1
  825. package/src/css/common.css +1 -1
  826. package/src/css/globals.css +72 -24
  827. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  828. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  829. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  830. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  831. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  832. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  833. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  834. package/src/hooks/useAsyncData.ts +3 -3
  835. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  836. package/src/hooks/useDebounce.ts +6 -10
  837. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  838. package/src/hooks/useDelayElapsed.ts +14 -10
  839. package/src/hooks/useElapsedTime.ts +3 -3
  840. package/src/hooks/useEventListener.ts +1 -2
  841. package/src/hooks/useInputHistory.ts +4 -7
  842. package/src/hooks/useInternalStateWithSync.ts +6 -6
  843. package/src/hooks/useResizeObserver.ts +4 -7
  844. package/src/hooks/useScript.ts +67 -70
  845. package/src/hooks/useStateForKey.ts +93 -0
  846. package/src/mount.tsx +20 -15
  847. package/src/plugins/__tests__/plugin-object-schemas.test.ts +104 -0
  848. package/src/plugins/core/RenderHTML.tsx +3 -5
  849. package/src/plugins/core/__test__/PluginSlot.test.tsx +202 -0
  850. package/src/plugins/core/__test__/RenderHTML-copy.test.tsx +60 -0
  851. package/src/plugins/core/__test__/registerReactComponent.test.ts +45 -0
  852. package/src/plugins/core/__test__/sanitize.test.ts +8 -0
  853. package/src/plugins/core/registerReactComponent.tsx +29 -29
  854. package/src/plugins/core/sanitize-html.ts +6 -0
  855. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  856. package/src/plugins/impl/DataEditorPlugin.tsx +34 -13
  857. package/src/plugins/impl/DataTablePlugin.tsx +69 -37
  858. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  859. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  860. package/src/plugins/impl/FormPlugin.tsx +1 -1
  861. package/src/plugins/impl/RangeSliderPlugin.tsx +16 -2
  862. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  863. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  864. package/src/plugins/impl/__tests__/DataEditorPlugin.test.ts +93 -4
  865. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +387 -1
  866. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  867. package/src/plugins/impl/__tests__/RangeSliderPlugin.test.tsx +109 -0
  868. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  869. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +153 -0
  870. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  871. package/src/plugins/impl/anywidget/model.ts +3 -1
  872. package/src/plugins/impl/anywidget/registry.ts +18 -14
  873. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  874. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  875. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  876. package/src/plugins/impl/chat/chat-ui.tsx +17 -15
  877. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  878. package/src/plugins/impl/common/error-banner.tsx +5 -3
  879. package/src/plugins/impl/data-editor/__tests__/column-types.test.ts +42 -0
  880. package/src/plugins/impl/data-editor/__tests__/editor-state.test.ts +52 -1
  881. package/src/plugins/impl/data-editor/__tests__/glide-data-editor.test.tsx +120 -4
  882. package/src/plugins/impl/data-editor/__tests__/glide-utils.test.ts +116 -0
  883. package/src/plugins/impl/data-editor/column-types.ts +55 -0
  884. package/src/plugins/impl/data-editor/editor-state.ts +18 -2
  885. package/src/plugins/impl/data-editor/glide-data-editor.tsx +74 -12
  886. package/src/plugins/impl/data-editor/glide-utils.ts +39 -38
  887. package/src/plugins/impl/data-editor/types.ts +5 -0
  888. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  889. package/src/plugins/impl/data-explorer/components/column-summary.tsx +4 -2
  890. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  891. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -2
  892. package/src/plugins/impl/data-frames/__tests__/sidebar.test.tsx +160 -0
  893. package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +66 -66
  894. package/src/plugins/impl/data-frames/panel.tsx +44 -16
  895. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  896. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  897. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  898. package/src/plugins/impl/panel/utils.ts +17 -0
  899. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +2 -5
  900. package/src/plugins/impl/plotly/selection.ts +25 -19
  901. package/src/plugins/impl/vega/VegaPlugin.tsx +2 -3
  902. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  903. package/src/plugins/layout/DownloadPlugin.tsx +7 -11
  904. package/src/plugins/layout/LazyPlugin.tsx +7 -4
  905. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  906. package/src/plugins/layout/__test__/DownloadPlugin.test.tsx +205 -0
  907. package/src/plugins/layout/__tests__/LazyPlugin.test.tsx +98 -0
  908. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  909. package/src/stories/button.stories.tsx +1 -0
  910. package/src/stories/cell-status.stories.tsx +29 -24
  911. package/src/stories/cell.stories.tsx +46 -46
  912. package/src/stories/data-table.stories.tsx +31 -0
  913. package/src/stories/editor.stories.tsx +28 -17
  914. package/src/stories/form-wrapper.stories.tsx +2 -2
  915. package/src/stories/layout/vertical/one-column.stories.tsx +28 -42
  916. package/src/stories/runtime-story.tsx +23 -17
  917. package/src/stories/select.stories.tsx +2 -2
  918. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  919. package/src/stories/theme.stories.tsx +4 -1
  920. package/src/theme/__tests__/islands-theme.test.tsx +276 -0
  921. package/src/theme/namespace.tsx +4 -3
  922. package/src/theme/useTheme.ts +71 -41
  923. package/src/utils/__tests__/arrays.test.ts +35 -0
  924. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  925. package/src/utils/__tests__/{createReducer.test.ts → createReducer.test.tsx} +127 -1
  926. package/src/utils/__tests__/download.test.tsx +183 -23
  927. package/src/utils/__tests__/numbers.test.ts +105 -1
  928. package/src/utils/__tests__/objects.test.ts +21 -0
  929. package/src/utils/arrays.ts +11 -2
  930. package/src/utils/blob.ts +1 -1
  931. package/src/utils/createReducer.ts +115 -79
  932. package/src/utils/download.ts +67 -6
  933. package/src/utils/maps.ts +0 -3
  934. package/src/utils/numbers.ts +79 -18
  935. package/src/utils/objects.ts +4 -9
  936. package/src/utils/time.ts +10 -0
  937. package/src/utils/tracer.ts +9 -5
  938. package/src/utils/zod-utils.ts +1 -3
  939. package/dist/_baseMerge-BiGRwuqv.js +0 -165
  940. package/dist/any-language-editor-D4q-IFcM.js +0 -76
  941. package/dist/architecture-7GRP2DOG-BWLylgiA.js +0 -3
  942. package/dist/assets/__vite-browser-external-BXYkiFsk.js +0 -1
  943. package/dist/assets/worker-HPY9fbhz.js +0 -114
  944. package/dist/classDiagram-ZZMXUADV-DXdCCDOg.js +0 -30
  945. package/dist/classDiagram-v2-VYDZK3BY-DyZqrtLP.js +0 -30
  946. package/dist/code-visibility-UR3IN3fl.js +0 -138
  947. package/dist/cynefin-OW5HDTMX-obq-i80l.js +0 -3
  948. package/dist/dropdown-menu-BuE2b8Yb.js +0 -4678
  949. package/dist/eventmodeling-NTZA5JFV-D7nP7Uyu.js +0 -3
  950. package/dist/feedback-button-DGkGODB3.js +0 -379
  951. package/dist/flowDiagram-HODETNUW-DQ4JuadE.js +0 -16
  952. package/dist/gitGraph-4MIJSDKK-Dy9uQXGh.js +0 -3
  953. package/dist/hasIn-o2Us0uVx.js +0 -132
  954. package/dist/html-to-image-BsG_zyNW.js +0 -3251
  955. package/dist/info-A6RAGUB7-DKoayiHY.js +0 -3
  956. package/dist/isArrayLikeObject-BZl-15ir.js +0 -450
  957. package/dist/isEmpty-BSZ1LaUo.js +0 -35
  958. package/dist/isSymbol-kQcfYbNX.js +0 -8
  959. package/dist/label-DoysmLMW.js +0 -281
  960. package/dist/mermaid-HWGCJPDP-Du3-HPQA.js +0 -2
  961. package/dist/packet-AYTQ26CC-Yw7ajchJ.js +0 -3
  962. package/dist/pick-CJeoxYOS.js +0 -51
  963. package/dist/pie-WAS4IAKB-BKdKOAkL.js +0 -3
  964. package/dist/purify.es-DMv5zIkJ.js +0 -866
  965. package/dist/radar-RG4KPBEZ-ScR_D_Ts.js +0 -3
  966. package/dist/railroad-74A4TZTK-DQvPSdo5.js +0 -3
  967. package/dist/railroad-abnf-HS5TGJTU-CsJQG2O8.js +0 -3
  968. package/dist/railroad-ebnf-LZEXJU2U-BgEobkSn.js +0 -3
  969. package/dist/railroad-peg-WCYAUIDC-DjLAjofI.js +0 -3
  970. package/dist/select-DemoKT4G.js +0 -1344
  971. package/dist/stateDiagram-v2-MP3YSRHH-1OQRe6iA.js +0 -30
  972. package/dist/swimlanesDiagram-VR7AAH4N-CszjkrKo.js +0 -30
  973. package/dist/toDate-gyi35XS2.js +0 -1185
  974. package/dist/toast-PkdZraM8.js +0 -1830
  975. package/dist/treeView-Q6P3EWNA-DuRV97YF.js +0 -3
  976. package/dist/treemap-WGGIJYW6-AsLq-uwC.js +0 -3
  977. package/dist/use-restore-focus-CKjfFjLM.js +0 -6369
  978. package/dist/useLifecycle-Dfq8UD-b.js +0 -169
  979. package/dist/useTheme-Bu5bBgrh.js +0 -904
  980. package/dist/wardley-WFR3VGLG-Na-tFhQJ.js +0 -3
  981. package/src/components/chat/reasoning-accordion.tsx +0 -61
  982. package/src/components/chat/tool-call/tool-error-card.tsx +0 -81
  983. package/src/components/chat/tool-call/tool-history-row.tsx +0 -174
  984. package/src/components/editor/ai/__tests__/staged-cell-submission.test.ts +0 -106
  985. package/src/components/editor/ai/add-cell-with-ai.tsx +0 -506
  986. package/src/components/editor/ai/staged-cell-submission.ts +0 -46
  987. package/src/components/editor/cell/__tests__/StagedAICell.test.tsx +0 -64
  988. package/src/components/editor/cell/code/__tests__/rtc-editor-config.test.ts +0 -50
  989. package/src/components/editor/chrome/wrapper/footer-items/rtc-status.tsx +0 -65
  990. package/src/components/editor/navigation/__tests__/state.test.ts +0 -81
  991. package/src/components/editor/navigation/state.ts +0 -65
  992. package/src/components/icons/loading-ellipsis.tsx +0 -46
  993. package/src/core/ai/tools/__tests__/run-cells-tool.test.ts +0 -656
  994. package/src/core/ai/tools/run-cells-tool.ts +0 -303
  995. package/src/core/attachments/state.ts +0 -18
  996. package/src/core/cells/__tests__/apply-transaction.test.ts +0 -991
  997. package/src/core/cells/__tests__/document-changes.test.ts +0 -1140
  998. package/src/core/cells/__tests__/document-roundtrip.test.ts +0 -392
  999. package/src/core/cells/document-changes.ts +0 -944
  1000. package/src/core/codemirror/rtc/extension.ts +0 -453
  1001. package/src/core/codemirror/rtc/loro/__tests__/sync.test.ts +0 -52
  1002. package/src/core/codemirror/rtc/loro/awareness.ts +0 -531
  1003. package/src/core/codemirror/rtc/loro/colors.ts +0 -38
  1004. package/src/core/codemirror/rtc/loro/sync.ts +0 -168
  1005. package/src/core/host/AGENTS.md +0 -11
  1006. package/src/core/host/__tests__/events.test.ts +0 -46
  1007. package/src/core/host/__tests__/fixtures.ts +0 -39
  1008. package/src/core/host/__tests__/notebook-client.test.ts +0 -433
  1009. package/src/core/host/__tests__/protocol.test.ts +0 -229
  1010. package/src/core/host/__tests__/stream.test.ts +0 -209
  1011. package/src/core/host/connection.ts +0 -81
  1012. package/src/core/host/events.ts +0 -61
  1013. package/src/core/host/notebook-client.ts +0 -290
  1014. package/src/core/host/protocol.ts +0 -1948
  1015. package/src/core/host/resources/resource.ts +0 -47
  1016. package/src/core/host/stream.ts +0 -211
  1017. package/src/core/rtc/state.ts +0 -34
  1018. package/src/core/state/__tests__/jotai.test.ts +0 -25
  1019. package/src/core/wasm/worker/message-buffer.ts +0 -33
  1020. package/src/core/websocket/__tests__/createConnectionTransport.test.ts +0 -49
  1021. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +0 -170
  1022. package/src/core/websocket/state.ts +0 -7
  1023. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  1024. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -335
  1025. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  1026. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  1027. package/src/core/websocket/transports/basic.ts +0 -102
  1028. package/src/core/websocket/transports/create.ts +0 -31
  1029. package/src/core/websocket/transports/sse.ts +0 -308
  1030. package/src/core/websocket/transports/transport.ts +0 -50
  1031. package/src/core/websocket/transports/ws.ts +0 -98
  1032. package/src/core/websocket/useNotebookClient.ts +0 -88
  1033. package/src/utils/__tests__/id-tree.test.ts +0 -2145
  1034. package/src/utils/id-tree.tsx +0 -1134
  1035. /package/src/{core/attachments/toast.ts → components/editor/agent-toast.ts} +0 -0
  1036. /package/src/core/{websocket → session}/connection-utils.ts +0 -0
@@ -1,32 +1,32 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
+ import { defaultUserConfig } from "@/core/config/config-schema";
4
+
3
5
  import { python } from "@codemirror/lang-python";
4
6
  import { EditorState } from "@codemirror/state";
5
7
  import { EditorView } from "@codemirror/view";
6
8
  import { createStore } from "jotai";
9
+ import { createRef } from "react";
7
10
  import {
8
11
  afterAll,
9
- afterEach,
10
12
  beforeAll,
13
+ afterEach,
11
14
  beforeEach,
12
15
  describe,
13
16
  expect,
14
17
  it,
15
18
  vi,
16
19
  } from "vitest";
20
+ import { MockExecutions } from "@/__mocks__/executions";
17
21
  import { MockNotebook } from "@/__mocks__/notebook";
18
22
  import { cellId } from "@/__tests__/branded";
19
23
  import type { CellHandle } from "@/components/editor/notebook-cell";
20
- import { CellId, SCRATCH_CELL_ID, SETUP_CELL_ID } from "@/core/cells/ids";
24
+ import { CellId, SCRATCH_CELL_ID } from "@/core/cells/ids";
21
25
  import { foldAllBulk, unfoldAllBulk } from "@/core/codemirror/editing/commands";
22
26
  import { adaptiveLanguageConfiguration } from "@/core/codemirror/language/extension";
23
27
  import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
24
28
  import type { OutputMessage } from "@/core/kernel/messages";
25
- import {
26
- type CollapsibleTree,
27
- MultiColumn,
28
- type CellColumnId,
29
- } from "@/utils/id-tree";
29
+ import type { CellConfig } from "@/core/network/types";
30
30
  import type { Seconds } from "@/utils/time";
31
31
  import {
32
32
  exportedForTesting,
@@ -34,13 +34,12 @@ import {
34
34
  type NotebookState,
35
35
  notebookAtom,
36
36
  } from "../cells";
37
- import { exportedForTesting as documentTransactionTestExports } from "../document-changes";
38
37
  import {
39
- focusAndScrollCellIntoView,
40
- scrollToBottom,
41
- scrollToTop,
42
- } from "../scrollCellIntoView";
43
- import type { CellData } from "../types";
38
+ type CellData,
39
+ createCell,
40
+ createCellConfig,
41
+ createCellRuntimeState,
42
+ } from "../types";
44
43
 
45
44
  vi.mock("@/core/codemirror/editing/commands", () => ({
46
45
  foldAllBulk: vi.fn(),
@@ -60,9 +59,6 @@ vi.mock("../scrollCellIntoView", async (importOriginal) => {
60
59
  };
61
60
  });
62
61
 
63
- const FIRST_COLUMN = 0;
64
- const SECOND_COLUMN = 1;
65
-
66
62
  const { initialNotebookState, reducer, createActions } = exportedForTesting;
67
63
 
68
64
  function formatCells(notebook: NotebookState) {
@@ -78,14 +74,7 @@ function formatCells(notebook: NotebookState) {
78
74
  const cells = cellsId.map((cellId) => cellData[cellId]);
79
75
  return cells.map((cell) => printCell(cell)).join("\n\n");
80
76
  };
81
- const printColumn = (column: CollapsibleTree<CellId>, columnIdx: number) => {
82
- return `> col ${columnIdx}\n${printCells(column.inOrderIds)}`;
83
- };
84
- const columns = cellIds.getColumns();
85
- if (columns.length > 1) {
86
- return wrap(columns.map(printColumn).join("\n\n"));
87
- }
88
- return wrap(printCells(cellIds.inOrderIds));
77
+ return wrap(printCells(cellIds));
89
78
  }
90
79
 
91
80
  function createEditor(content: string) {
@@ -96,6 +85,7 @@ function createEditor(content: string) {
96
85
  adaptiveLanguageConfiguration({
97
86
  cellId: cellId("cell1"),
98
87
  completionConfig: {
88
+ ...defaultUserConfig().completion,
99
89
  activate_on_typing: true,
100
90
  signature_hint_on_typing: false,
101
91
  copilot: false,
@@ -103,7 +93,7 @@ function createEditor(content: string) {
103
93
  },
104
94
  hotkeys: new OverridingHotkeyProvider({}),
105
95
  placeholderType: "marimo-import",
106
- lspConfig: {},
96
+ lspConfig: { ...defaultUserConfig().language_servers },
107
97
  }),
108
98
  ],
109
99
  });
@@ -121,8 +111,7 @@ describe("cell reducer", () => {
121
111
  let cells: ReturnType<typeof flattenTopLevelNotebookCells>;
122
112
  let firstCellId: CellId;
123
113
 
124
- const actions = createActions((action) => {
125
- state = reducer(state, action);
114
+ const mountEditors = () => {
126
115
  for (const [cellIdString, handle] of Object.entries(state.cellHandles)) {
127
116
  // @ts-expect-error - Typescript Object.entries doesn't know that keys are CellId
128
117
  const cid: CellId = cellIdString;
@@ -136,7 +125,83 @@ describe("cell reducer", () => {
136
125
  }
137
126
  }
138
127
  cells = flattenTopLevelNotebookCells(state);
139
- });
128
+ };
129
+ const actions = createActions((action) => {
130
+ state = reducer(state, action);
131
+ mountEditors();
132
+ });
133
+
134
+ /** Put a cell into the notebook the way the document would. */
135
+ function seedCell({
136
+ cellId,
137
+ before,
138
+ code = "",
139
+ name,
140
+ config,
141
+ newCellId,
142
+ hideCode,
143
+ autoFocus = true,
144
+ }: {
145
+ cellId: CellId | "__end__";
146
+ before: boolean;
147
+ code?: string;
148
+ name?: string;
149
+ config?: CellConfig;
150
+ newCellId?: CellId;
151
+ hideCode?: boolean;
152
+ autoFocus?: boolean;
153
+ }): CellId {
154
+ const cellIndex =
155
+ cellId === "__end__"
156
+ ? state.cellIds.length
157
+ : state.cellIds.indexOf(cellId);
158
+ const id = newCellId ?? CellId.create();
159
+ const cellIds = [...state.cellIds];
160
+ cellIds.splice(before ? cellIndex : cellIndex + 1, 0, id);
161
+ state = {
162
+ ...state,
163
+ cellIds,
164
+ cellData: {
165
+ ...state.cellData,
166
+ [id]: createCell({
167
+ id,
168
+ code,
169
+ name,
170
+ config: createCellConfig({
171
+ ...config,
172
+ ...(hideCode != null && { hide_code: hideCode }),
173
+ }),
174
+ }),
175
+ },
176
+ cellRuntime: { ...state.cellRuntime, [id]: createCellRuntimeState() },
177
+ cellHandles: { ...state.cellHandles, [id]: createRef() },
178
+ scrollKey: autoFocus ? id : null,
179
+ };
180
+ mountEditors();
181
+ return id;
182
+ }
183
+
184
+ /** Edit a cell's code as the document delivers it. */
185
+ function editCode(cellId: CellId, code: string) {
186
+ const cells = state.cellIds.map((id) => {
187
+ const cell = state.cellData[id];
188
+ return {
189
+ id,
190
+ code: id === cellId ? code : cell.code,
191
+ name: cell.name,
192
+ config: {
193
+ column: cell.config.column ?? null,
194
+ disabled: cell.config.disabled ?? false,
195
+ hide_code: cell.config.hide_code ?? false,
196
+ expand_output: cell.config.expand_output ?? false,
197
+ },
198
+ };
199
+ });
200
+ actions.reconcileDocument({
201
+ order: state.cellIds,
202
+ cells: new Map(cells.map((cell) => [cell.id, cell])),
203
+ });
204
+ }
140
205
 
141
206
  let i = 0;
142
207
  const originalCreate = CellId.create.bind(CellId);
@@ -151,907 +216,214 @@ describe("cell reducer", () => {
151
216
  i = 0;
152
217
 
153
218
  state = initialNotebookState();
154
- state.cellIds = MultiColumn.from([]);
155
- actions.createNewCell({ cellId: "__end__", before: false });
156
- firstCellId = state.cellIds.inOrderIds[0];
157
- });
158
-
159
- afterEach(() => {
160
- documentTransactionTestExports.cancelPendingChanges();
219
+ seedCell({ cellId: "__end__", before: false });
220
+ firstCellId = state.cellIds[0];
161
221
  });
162
222
 
163
223
  afterAll(() => {
164
224
  CellId.create = originalCreate;
165
225
  });
166
226
 
167
- it("can add a cell after another cell", () => {
168
- actions.createNewCell({
169
- cellId: firstCellId,
170
- before: false,
171
- });
227
+ it("can update a cell", () => {
228
+ editCode(firstCellId, "import numpy as np");
172
229
  expect(formatCells(state)).toMatchInlineSnapshot(`
173
230
  "
174
- [0] ''
175
-
176
- [1] ''
231
+ [0] 'import numpy as np'
177
232
  "
178
233
  `);
179
234
  });
180
235
 
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,
236
+ it("keeps error evidence after an edit and a new queued run", () => {
237
+ const code = "raise ValueError('old failure')";
238
+ const output: OutputMessage = {
239
+ channel: "marimo-error",
240
+ mimetype: "application/vnd.marimo+error",
241
+ data: [
242
+ {
243
+ type: "exception",
244
+ msg: "old failure",
245
+ exception_type: "ValueError",
246
+ raising_cell: null,
247
+ traceback: "<pre>ValueError: old failure</pre>",
248
+ },
249
+ ],
250
+ code,
251
+ timestamp: 1,
252
+ };
253
+ actions.handleCellMessage({
254
+ cell_id: firstCellId,
255
+ output,
256
+ console: {
257
+ channel: "stderr",
258
+ mimetype: "application/vnd.marimo+traceback",
259
+ data: "<pre>ValueError: old failure</pre>",
260
+ code,
261
+ timestamp: 1,
262
+ },
263
+ status: "idle",
264
+ timestamp: 1,
186
265
  });
187
- expect(formatCells(state)).toMatchInlineSnapshot(`
188
- "
189
- [0] ''
266
+ editCode(firstCellId, "print('new code')");
267
+ actions.prepareForRun({ cellId: firstCellId });
268
+ actions.handleCellMessage({
269
+ cell_id: firstCellId,
270
+ output: null,
271
+ console: null,
272
+ status: "queued",
273
+ timestamp: 2,
274
+ });
275
+ const runtime = state.cellRuntime[firstCellId];
276
+ expect(runtime.output).toEqual(output);
277
+ expect(runtime.consoleOutputs[0].code).toBe(code);
278
+ expect(runtime.consoleOutputs[0].data).toContain("old failure");
279
+ });
190
280
 
191
- [1] 'import numpy as np'
192
- "
193
- `);
281
+ it("can run cell and receive cell messages", () => {
282
+ // HAPPY PATH
283
+ /////////////////
284
+ // Initial state
285
+ let cell = cells[0];
286
+ expect(cell).toMatchSnapshot("initial state");
194
287
 
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
- });
288
+ // Update code
289
+ editCode(firstCellId, "import marimo as mo");
290
+ cell = cells[0];
291
+ expect(cell).toMatchSnapshot("code edited");
200
292
 
201
- it("can add a cell before another cell", () => {
202
- actions.createNewCell({
293
+ // Prepare for run
294
+ actions.prepareForRun({
203
295
  cellId: firstCellId,
204
- before: true,
205
296
  });
206
- expect(formatCells(state)).toMatchInlineSnapshot(`
207
- "
208
- [1] ''
297
+ cell = cells[0];
298
+ expect(cell).toMatchSnapshot("prepared for run");
209
299
 
210
- [0] ''
211
- "
212
- `);
213
- });
300
+ // Receive queued messages
301
+ actions.handleCellMessage({
302
+ cell_id: firstCellId,
303
+ output: undefined,
304
+ console: null,
305
+ status: "queued",
306
+ timestamp: new Date(10).getTime() as Seconds,
307
+ });
308
+ cell = cells[0];
309
+ expect(cell).toMatchSnapshot("queued message received");
214
310
 
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 },
311
+ // Receive running messages
312
+ actions.handleCellMessage({
313
+ cell_id: firstCellId,
314
+ output: undefined,
315
+ console: null,
316
+ status: "running",
317
+ timestamp: new Date(20).getTime() as Seconds,
222
318
  });
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
- });
319
+ cell = cells[0];
320
+ expect(cell).toMatchSnapshot("running message received");
228
321
 
229
- it("can delete a Python cell and undo delete", () => {
230
- actions.createNewCell({
231
- cellId: firstCellId,
232
- before: false,
322
+ // Console messages shouldn't transition status
323
+ actions.handleCellMessage({
324
+ cell_id: firstCellId,
325
+ output: undefined,
326
+ console: {
327
+ channel: "stdout",
328
+ mimetype: "text/plain",
329
+ data: "hello!",
330
+ timestamp: 0,
331
+ },
332
+ status: undefined,
333
+ timestamp: new Date(22).getTime() as Seconds,
233
334
  });
234
- actions.deleteCell({
235
- cellId: firstCellId,
335
+ cell = cells[0];
336
+ expect(cell).toMatchSnapshot("console message received");
337
+
338
+ // Receive output messages
339
+ actions.handleCellMessage({
340
+ cell_id: firstCellId,
341
+ output: {
342
+ channel: "output",
343
+ mimetype: "text/plain",
344
+ data: "ok",
345
+ timestamp: 0,
346
+ },
347
+ console: {
348
+ channel: "stdout",
349
+ mimetype: "text/plain",
350
+ data: "hello!",
351
+ timestamp: 0,
352
+ },
353
+ status: "idle",
354
+ timestamp: new Date(33).getTime() as Seconds,
236
355
  });
237
- expect(formatCells(state)).toMatchInlineSnapshot(`
238
- "
239
- [1] ''
240
- "
241
- `);
356
+ cell = cells[0];
357
+ expect(cell).toMatchSnapshot("output received");
242
358
 
243
- // undo
244
- actions.undoDeleteCell();
245
- expect(formatCells(state)).toMatchInlineSnapshot(`
246
- "
247
- [2] ''
359
+ // EDITING BACK AND FORTH
360
+ /////////////////
361
+ // Update code again
362
+ editCode(firstCellId, "import marimo as mo\nimport numpy");
363
+ cell = cells[0];
364
+ expect(cell).toMatchSnapshot("code edited again");
248
365
 
249
- [1] ''
250
- "
251
- `);
366
+ // Update code should be unedited
367
+ editCode(firstCellId, "import marimo as mo");
368
+ cell = cells[0];
369
+ expect(cell).toMatchSnapshot("code reverted");
252
370
 
253
- // Verify scrollKey is set to the restored cell
254
- expect(state.scrollKey).toBe("2");
255
- });
371
+ // Update code should be edited again
372
+ editCode(firstCellId, "import marimo as mo\nimport numpy");
373
+ cell = cells[0];
374
+ expect(cell).toMatchSnapshot("code changed after revert");
256
375
 
257
- it("can delete a SQL cell and undo delete", () => {
258
- actions.createNewCell({
376
+ // ERROR RESPONSE
377
+ /////////////////
378
+ // Prepare for run
379
+ actions.prepareForRun({
259
380
  cellId: firstCellId,
260
- before: false,
261
- code: `df = mo.sql("""SELECT * FROM table""")`,
262
381
  });
263
- const newCellId = state.cellIds.getColumns()[0].atOrThrow(1);
264
- actions.deleteCell({
265
- cellId: newCellId,
382
+ cell = cells[0];
383
+ expect(cell.output).not.toBe(null); // keep old output
384
+ // Queue
385
+ actions.handleCellMessage({
386
+ cell_id: firstCellId,
387
+ output: undefined,
388
+ console: null,
389
+ status: "queued",
390
+ timestamp: new Date(40).getTime() as Seconds,
266
391
  });
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}""")`,
392
+ cell = cells[0];
393
+ expect(cell.output).not.toBe(null); // keep old output
394
+ // Running
395
+ actions.handleCellMessage({
396
+ cell_id: firstCellId,
397
+ output: undefined,
398
+ console: null,
399
+ status: "running",
400
+ timestamp: new Date(50).getTime() as Seconds,
290
401
  });
291
- const newCellId = state.cellIds.getColumns()[0].atOrThrow(1);
292
- actions.deleteCell({
293
- cellId: newCellId,
402
+ cell = cells[0];
403
+ expect(cell.output).not.toBe(null); // keep old output
404
+ // Receive error
405
+ actions.handleCellMessage({
406
+ cell_id: firstCellId,
407
+ output: {
408
+ channel: "marimo-error",
409
+ mimetype: "application/vnd.marimo+error",
410
+ data: [
411
+ { type: "exception", exception_type: "ValueError", msg: "Oh no!" },
412
+ ],
413
+ timestamp: 0,
414
+ },
415
+ console: null,
416
+ status: "idle",
417
+ timestamp: new Date(61).getTime() as Seconds,
294
418
  });
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
- });
419
+ cell = cells[0];
420
+ expect(cell.output).not.toBe(null); // keep old output
421
+ expect(cell).toMatchSnapshot("error response received");
311
422
 
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
- it("can update a cell", () => {
343
- actions.updateCellCode({
344
- cellId: firstCellId,
345
- code: "import numpy as np",
346
- formattingChange: false,
347
- });
348
- expect(formatCells(state)).toMatchInlineSnapshot(`
349
- "
350
- [0] 'import numpy as np'
351
- "
352
- `);
353
- });
354
-
355
- it("can move a cell", () => {
356
- actions.createNewCell({
357
- cellId: firstCellId,
358
- before: false,
359
- });
360
- expect(formatCells(state)).toMatchInlineSnapshot(`
361
- "
362
- [0] ''
363
-
364
- [1] ''
365
- "
366
- `);
367
-
368
- // move first cell to the end
369
- actions.moveCell({
370
- cellId: firstCellId,
371
- before: false,
372
- });
373
- expect(formatCells(state)).toMatchInlineSnapshot(`
374
- "
375
- [1] ''
376
-
377
- [0] ''
378
- "
379
- `);
380
-
381
- // move it back
382
- actions.moveCell({
383
- cellId: firstCellId,
384
- before: true,
385
- });
386
- expect(formatCells(state)).toMatchInlineSnapshot(`
387
- "
388
- [0] ''
389
-
390
- [1] ''
391
- "
392
- `);
393
-
394
- // Add a column breakpoint to test left/right movement
395
- actions.addColumnBreakpoint({ cellId: cellId("1") });
396
- expect(formatCells(state)).toMatchInlineSnapshot(`
397
- "
398
- > col 0
399
- [0] ''
400
-
401
- > col 1
402
- [1] ''
403
- "
404
- `);
405
-
406
- // Move cell right
407
- actions.moveCell({
408
- cellId: firstCellId,
409
- direction: "right",
410
- });
411
- expect(formatCells(state)).toMatchInlineSnapshot(`
412
- "
413
- > col 0
414
-
415
-
416
- > col 1
417
- [0] ''
418
-
419
- [1] ''
420
- "
421
- `);
422
-
423
- // Move cell left
424
- actions.moveCell({
425
- cellId: firstCellId,
426
- direction: "left",
427
- });
428
- expect(formatCells(state)).toMatchInlineSnapshot(`
429
- "
430
- > col 0
431
- [0] ''
432
-
433
- > col 1
434
- [1] ''
435
- "
436
- `);
437
-
438
- // Try to move cell left when it's already in leftmost column (should noop)
439
- actions.moveCell({
440
- cellId: firstCellId,
441
- direction: "left",
442
- });
443
- expect(formatCells(state)).toMatchInlineSnapshot(`
444
- "
445
- > col 0
446
- [0] ''
447
-
448
- > col 1
449
- [1] ''
450
- "
451
- `);
452
-
453
- // Try to move cell right when it's already in rightmost column (should noop)
454
- actions.moveCell({
455
- cellId: cellId("1"),
456
- direction: "right",
457
- });
458
- expect(formatCells(state)).toMatchInlineSnapshot(`
459
- "
460
- > col 0
461
- [0] ''
462
-
463
- > col 1
464
- [1] ''
465
- "
466
- `);
467
- });
468
-
469
- it("can drag and drop a cell", () => {
470
- actions.createNewCell({
471
- cellId: firstCellId,
472
- before: false,
473
- });
474
- actions.createNewCell({
475
- cellId: cellId("1"),
476
- before: false,
477
- });
478
- expect(formatCells(state)).toMatchInlineSnapshot(`
479
- "
480
- [0] ''
481
-
482
- [1] ''
483
-
484
- [2] ''
485
- "
486
- `);
487
-
488
- // drag first cell to the end
489
- actions.dropCellOverCell({
490
- cellId: firstCellId,
491
- overCellId: cellId("2"),
492
- });
493
- expect(formatCells(state)).toMatchInlineSnapshot(`
494
- "
495
- [1] ''
496
-
497
- [2] ''
498
-
499
- [0] ''
500
- "
501
- `);
502
-
503
- // drag it back to the middle
504
- actions.dropCellOverCell({
505
- cellId: firstCellId,
506
- overCellId: cellId("2"),
507
- });
508
- expect(formatCells(state)).toMatchInlineSnapshot(`
509
- "
510
- [1] ''
511
-
512
- [0] ''
513
-
514
- [2] ''
515
- "
516
- `);
517
- });
518
-
519
- it("can move a cell to an exact index within a column", () => {
520
- actions.createNewCell({
521
- cellId: firstCellId,
522
- before: false,
523
- });
524
- actions.createNewCell({
525
- cellId: cellId("1"),
526
- before: false,
527
- });
528
-
529
- const columnId = state.cellIds.atOrThrow(0).id;
530
- actions.moveCellToIndex({
531
- cellId: firstCellId,
532
- columnId,
533
- index: 3,
534
- });
535
-
536
- expect(formatCells(state)).toMatchInlineSnapshot(`
537
- "
538
- [1] ''
539
-
540
- [2] ''
541
-
542
- [0] ''
543
- "
544
- `);
545
- });
546
-
547
- it("can move a cell to an exact index across columns", () => {
548
- actions.createNewCell({
549
- cellId: firstCellId,
550
- before: false,
551
- });
552
- actions.createNewCell({
553
- cellId: cellId("1"),
554
- before: false,
555
- });
556
- actions.addColumnBreakpoint({ cellId: cellId("1") });
557
-
558
- const secondColumnId = state.cellIds.atOrThrow(1).id;
559
- actions.moveCellToIndex({
560
- cellId: firstCellId,
561
- columnId: secondColumnId,
562
- index: 1,
563
- });
564
-
565
- expect(formatCells(state)).toMatchInlineSnapshot(`
566
- "
567
- > col 0
568
-
569
-
570
- > col 1
571
- [1] ''
572
-
573
- [0] ''
574
-
575
- [2] ''
576
- "
577
- `);
578
- });
579
-
580
- it("moveCellToIndex is a no-op when moving to the same position", () => {
581
- actions.createNewCell({
582
- cellId: firstCellId,
583
- before: false,
584
- });
585
- actions.createNewCell({
586
- cellId: cellId("1"),
587
- before: false,
588
- });
589
-
590
- const columnId = state.cellIds.atOrThrow(0).id;
591
- const before = formatCells(state);
592
-
593
- actions.moveCellToIndex({
594
- cellId: firstCellId,
595
- columnId,
596
- index: 0,
597
- });
598
-
599
- expect(formatCells(state)).toBe(before);
600
- });
601
-
602
- it("moveCellToIndex is a no-op for an invalid columnId", () => {
603
- actions.createNewCell({
604
- cellId: firstCellId,
605
- before: false,
606
- });
607
-
608
- const before = formatCells(state);
609
-
610
- actions.moveCellToIndex({
611
- cellId: firstCellId,
612
- columnId: "nonexistent-column" as CellColumnId,
613
- index: 0,
614
- });
615
-
616
- expect(formatCells(state)).toBe(before);
617
- });
618
-
619
- it("can move multiple cells relative to target", () => {
620
- actions.createNewCell({
621
- cellId: firstCellId,
622
- before: false,
623
- });
624
- actions.createNewCell({
625
- cellId: cellId("1"),
626
- before: false,
627
- });
628
- expect(formatCells(state)).toMatchInlineSnapshot(`
629
- "
630
- [0] ''
631
-
632
- [1] ''
633
-
634
- [2] ''
635
- "
636
- `);
637
-
638
- // Move first two cells after the third
639
- actions.moveCellsRelativeTo({
640
- cellIds: [firstCellId, cellId("1")],
641
- targetCellId: cellId("2"),
642
- position: "after",
643
- });
644
- expect(formatCells(state)).toMatchInlineSnapshot(`
645
- "
646
- [2] ''
647
-
648
- [0] ''
649
-
650
- [1] ''
651
- "
652
- `);
653
- });
654
-
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
- it("keeps error evidence after an edit and a new queued run", () => {
793
- const code = "raise ValueError('old failure')";
794
- const output: OutputMessage = {
795
- channel: "marimo-error",
796
- mimetype: "application/vnd.marimo+error",
797
- data: [
798
- {
799
- type: "exception",
800
- msg: "old failure",
801
- exception_type: "ValueError",
802
- raising_cell: null,
803
- traceback: "<pre>ValueError: old failure</pre>",
804
- },
805
- ],
806
- code,
807
- timestamp: 1,
808
- };
809
- actions.handleCellMessage({
810
- cell_id: firstCellId,
811
- output,
812
- console: {
813
- channel: "stderr",
814
- mimetype: "application/vnd.marimo+traceback",
815
- data: "<pre>ValueError: old failure</pre>",
816
- code,
817
- timestamp: 1,
818
- },
819
- status: "idle",
820
- stale_inputs: null,
821
- timestamp: 1,
822
- });
823
- actions.updateCellCode({
824
- cellId: firstCellId,
825
- code: "print('new code')",
826
- formattingChange: false,
827
- });
828
- actions.prepareForRun({ cellId: firstCellId });
829
- actions.handleCellMessage({
830
- cell_id: firstCellId,
831
- output: null,
832
- console: null,
833
- status: "queued",
834
- stale_inputs: null,
835
- timestamp: 2,
836
- });
837
- const runtime = state.cellRuntime[firstCellId];
838
- expect(state.cellData[firstCellId].lastCodeRun).toBe("print('new code')");
839
- expect(runtime.output).toEqual(output);
840
- expect(runtime.consoleOutputs[0].code).toBe(code);
841
- expect(runtime.consoleOutputs[0].data).toContain("old failure");
842
- });
843
-
844
- it("can run cell and receive cell messages", () => {
845
- // HAPPY PATH
846
- /////////////////
847
- // Initial state
848
- let cell = cells[0];
849
- expect(cell.status).toBe("idle");
850
- expect(cell.lastCodeRun).toBe(null);
851
- expect(cell.edited).toBe(false);
852
- expect(cell).toMatchSnapshot("initial state");
853
-
854
- // Update code
855
- actions.updateCellCode({
856
- cellId: firstCellId,
857
- code: "import marimo as mo",
858
- formattingChange: false,
859
- });
860
- cell = cells[0];
861
- expect(cell.status).toBe("idle");
862
- expect(cell.lastCodeRun).toBe(null);
863
- expect(cell.edited).toBe(true);
864
- expect(cell).toMatchSnapshot("code edited");
865
-
866
- // Prepare for run
867
- actions.prepareForRun({
868
- cellId: firstCellId,
869
- });
870
- cell = cells[0];
871
- expect(cell.status).toBe("queued");
872
- expect(cell.lastCodeRun).toBe("import marimo as mo");
873
- expect(cell.edited).toBe(false);
874
- expect(cell).toMatchSnapshot("prepared for run");
875
-
876
- // Receive queued messages
877
- actions.handleCellMessage({
878
- cell_id: firstCellId,
879
- output: undefined,
880
- console: null,
881
- status: "queued",
882
- stale_inputs: null,
883
- timestamp: new Date(10).getTime() as Seconds,
884
- });
885
- cell = cells[0];
886
- expect(cell.status).toBe("queued");
887
- expect(cell.lastCodeRun).toBe("import marimo as mo");
888
- expect(cell.edited).toBe(false);
889
- expect(cell.runElapsedTimeMs).toBe(null);
890
- expect(cell.runStartTimestamp).toBe(null);
891
- expect(cell.lastRunStartTimestamp).toBe(null);
892
- expect(cell).toMatchSnapshot("queued message received");
893
-
894
- // Receive running messages
895
- actions.handleCellMessage({
896
- cell_id: firstCellId,
897
- output: undefined,
898
- console: null,
899
- status: "running",
900
- stale_inputs: null,
901
- timestamp: new Date(20).getTime() as Seconds,
902
- });
903
- cell = cells[0];
904
- expect(cell.status).toBe("running");
905
- expect(cell.lastCodeRun).toBe("import marimo as mo");
906
- expect(cell.edited).toBe(false);
907
- expect(cell.runElapsedTimeMs).toBe(null);
908
- expect(cell.runStartTimestamp).toBe(20);
909
- expect(cell.lastRunStartTimestamp).toBe(20);
910
- expect(cell).toMatchSnapshot("running message received");
911
-
912
- // Console messages shouldn't transition status
913
- actions.handleCellMessage({
914
- cell_id: firstCellId,
915
- output: undefined,
916
- console: {
917
- channel: "stdout",
918
- mimetype: "text/plain",
919
- data: "hello!",
920
- timestamp: 0,
921
- },
922
- status: undefined,
923
- stale_inputs: null,
924
- timestamp: new Date(22).getTime() as Seconds,
925
- });
926
- cell = cells[0];
927
- expect(cell.status).toBe("running");
928
- expect(cell.edited).toBe(false);
929
- expect(cell.runElapsedTimeMs).toBe(null);
930
- expect(cell.runStartTimestamp).toBe(20);
931
- expect(cell.lastRunStartTimestamp).toBe(20);
932
- expect(cell).toMatchSnapshot("console message received");
933
-
934
- // Receive output messages
935
- actions.handleCellMessage({
936
- cell_id: firstCellId,
937
- output: {
938
- channel: "output",
939
- mimetype: "text/plain",
940
- data: "ok",
941
- timestamp: 0,
942
- },
943
- console: {
944
- channel: "stdout",
945
- mimetype: "text/plain",
946
- data: "hello!",
947
- timestamp: 0,
948
- },
949
- status: "idle",
950
- stale_inputs: null,
951
- timestamp: new Date(33).getTime() as Seconds,
952
- });
953
- cell = cells[0];
954
- expect(cell.status).toBe("idle");
955
- expect(cell.edited).toBe(false);
956
- expect(cell.runElapsedTimeMs).toBe(13_000);
957
- expect(cell.runStartTimestamp).toBe(null);
958
- expect(cell.lastRunStartTimestamp).toBe(20);
959
- expect(cell).toMatchSnapshot("output received");
960
-
961
- // EDITING BACK AND FORTH
962
- /////////////////
963
- // Update code again
964
- actions.updateCellCode({
965
- cellId: firstCellId,
966
- code: "import marimo as mo\nimport numpy",
967
- formattingChange: false,
968
- });
969
- cell = cells[0];
970
- expect(cell.status).toBe("idle");
971
- expect(cell.edited).toBe(true);
972
- expect(cell).toMatchSnapshot("code edited again");
973
-
974
- // Update code should be unedited
975
- actions.updateCellCode({
976
- cellId: firstCellId,
977
- code: "import marimo as mo",
978
- formattingChange: false,
979
- });
980
- cell = cells[0];
981
- expect(cell.edited).toBe(false);
982
- expect(cell).toMatchSnapshot("code reverted");
983
-
984
- // Update code should be edited again
985
- actions.updateCellCode({
986
- cellId: firstCellId,
987
- code: "import marimo as mo\nimport numpy",
988
- formattingChange: false,
989
- });
990
- cell = cells[0];
991
- expect(cell.status).toBe("idle");
992
- expect(cell.lastCodeRun).toBe("import marimo as mo");
993
- expect(cell.edited).toBe(true);
994
- expect(cell).toMatchSnapshot("code changed after revert");
995
-
996
- // ERROR RESPONSE
997
- /////////////////
998
- // Prepare for run
999
- actions.prepareForRun({
1000
- cellId: firstCellId,
1001
- });
1002
- cell = cells[0];
1003
- expect(cell.output).not.toBe(null); // keep old output
1004
- // Queue
1005
- actions.handleCellMessage({
1006
- cell_id: firstCellId,
1007
- output: undefined,
1008
- console: null,
1009
- status: "queued",
1010
- stale_inputs: null,
1011
- timestamp: new Date(40).getTime() as Seconds,
1012
- });
1013
- cell = cells[0];
1014
- expect(cell.output).not.toBe(null); // keep old output
1015
- // Running
1016
- actions.handleCellMessage({
1017
- cell_id: firstCellId,
1018
- output: undefined,
1019
- console: null,
1020
- status: "running",
1021
- stale_inputs: null,
1022
- timestamp: new Date(50).getTime() as Seconds,
1023
- });
1024
- cell = cells[0];
1025
- expect(cell.output).not.toBe(null); // keep old output
1026
- // Receive error
1027
- actions.handleCellMessage({
1028
- cell_id: firstCellId,
1029
- output: {
1030
- channel: "marimo-error",
1031
- mimetype: "application/vnd.marimo+error",
1032
- data: [
1033
- { type: "exception", exception_type: "ValueError", msg: "Oh no!" },
1034
- ],
1035
- timestamp: 0,
1036
- },
1037
- console: null,
1038
- status: "idle",
1039
- stale_inputs: null,
1040
- timestamp: new Date(61).getTime() as Seconds,
1041
- });
1042
- cell = cells[0];
1043
- expect(cell.output).not.toBe(null); // keep old output
1044
- expect(cell.status).toBe("idle");
1045
- expect(cell.lastCodeRun).toBe("import marimo as mo\nimport numpy");
1046
- expect(cell.edited).toBe(false);
1047
- expect(cell.runElapsedTimeMs).toBe(11_000);
1048
- expect(cell.runStartTimestamp).toBe(null);
1049
- expect(cell).toMatchSnapshot("error response received");
1050
-
1051
- // INTERRUPT RESPONSE
1052
- /////////////////
1053
- // Prepare for run
1054
- actions.prepareForRun({
423
+ // INTERRUPT RESPONSE
424
+ /////////////////
425
+ // Prepare for run
426
+ actions.prepareForRun({
1055
427
  cellId: firstCellId,
1056
428
  });
1057
429
  cell = cells[0];
@@ -1062,7 +434,6 @@ describe("cell reducer", () => {
1062
434
  output: undefined,
1063
435
  console: null,
1064
436
  status: "queued",
1065
- stale_inputs: null,
1066
437
  timestamp: new Date(40).getTime() as Seconds,
1067
438
  });
1068
439
  cell = cells[0];
@@ -1073,7 +444,6 @@ describe("cell reducer", () => {
1073
444
  output: undefined,
1074
445
  console: null,
1075
446
  status: "running",
1076
- stale_inputs: null,
1077
447
  timestamp: new Date(50).getTime() as Seconds,
1078
448
  });
1079
449
  cell = cells[0];
@@ -1089,100 +459,23 @@ describe("cell reducer", () => {
1089
459
  },
1090
460
  console: null,
1091
461
  status: "idle",
1092
- stale_inputs: null,
1093
462
  timestamp: new Date(61).getTime() as Seconds,
1094
463
  });
1095
464
  cell = cells[0];
1096
- expect(cell.status).toBe("idle");
1097
- expect(cell.lastCodeRun).toBe(cell.code);
1098
- expect(cell.edited).toBe(false);
1099
- expect(cell.runElapsedTimeMs).toBe(11_000);
1100
- expect(cell.runStartTimestamp).toBe(null);
1101
465
  expect(cell).toMatchSnapshot("interruption response received");
1102
466
  });
1103
467
 
1104
- it("errors reset status to idle", () => {
1105
- // Initial state
1106
- let cell = cells[0];
1107
- expect(cell.status).toBe("idle");
1108
- expect(cell.lastCodeRun).toBe(null);
1109
- expect(cell.edited).toBe(false);
1110
- expect(cell).toMatchSnapshot("initial state");
1111
-
1112
- // Update code
1113
- actions.updateCellCode({
1114
- cellId: firstCellId,
1115
- code: "import marimo as mo",
1116
- formattingChange: false,
1117
- });
1118
- cell = cells[0];
1119
- expect(cell.status).toBe("idle");
1120
- expect(cell.lastCodeRun).toBe(null);
1121
- expect(cell.edited).toBe(true);
1122
- expect(cell).toMatchSnapshot("code edited");
1123
-
1124
- // Prepare for run
1125
- actions.prepareForRun({
1126
- cellId: firstCellId,
1127
- });
1128
- cell = cells[0];
1129
- expect(cell.status).toBe("queued");
1130
- expect(cell.lastCodeRun).toBe("import marimo as mo");
1131
- expect(cell.edited).toBe(false);
1132
- expect(cell).toMatchSnapshot("prepared for run");
1133
-
1134
- // ERROR RESPONSE
1135
- //
1136
- // should reset status to idle
1137
- /////////////////
1138
- // Prepare for run
1139
- actions.prepareForRun({
1140
- cellId: firstCellId,
1141
- });
1142
- cell = cells[0];
1143
- // Receive error
1144
- actions.handleCellMessage({
1145
- cell_id: firstCellId,
1146
- output: {
1147
- channel: "marimo-error",
1148
- mimetype: "application/vnd.marimo+error",
1149
- data: [
1150
- { type: "exception", exception_type: "SyntaxError", msg: "Oh no!" },
1151
- ],
1152
- timestamp: 0,
1153
- },
1154
- console: null,
1155
- stale_inputs: null,
1156
- timestamp: new Date(61).getTime() as Seconds,
1157
- });
1158
- cell = cells[0];
1159
- expect(cell.status).toBe("idle");
1160
- expect(cell.lastCodeRun).toBe("import marimo as mo");
1161
- expect(cell.edited).toBe(false);
1162
- expect(cell.runElapsedTimeMs).toBe(null);
1163
- expect(cell.runStartTimestamp).toBe(null);
1164
- expect(cell).toMatchSnapshot("error response received");
1165
- });
1166
-
1167
468
  it("can run a stale cell", () => {
1168
469
  // Update code of first cell
1169
- actions.updateCellCode({
1170
- cellId: firstCellId,
1171
- code: "import marimo as mo",
1172
- formattingChange: false,
1173
- });
470
+ editCode(firstCellId, "import marimo as mo");
1174
471
  // Add cell
1175
- actions.createNewCell({
472
+ seedCell({
1176
473
  cellId: firstCellId,
1177
474
  before: false,
1178
475
  });
1179
476
  const secondCell = cells[1].id;
1180
- // Update code
1181
- actions.updateCellCode({
1182
- code: "mo.slider()",
1183
- cellId: secondCell,
1184
- formattingChange: false,
1185
- });
477
+ // Update code
478
+ editCode(secondCell, "mo.slider()");
1186
479
 
1187
480
  // Prepare for run
1188
481
  actions.prepareForRun({
@@ -1195,15 +488,9 @@ describe("cell reducer", () => {
1195
488
  output: undefined,
1196
489
  console: null,
1197
490
  status: "queued",
1198
- stale_inputs: null,
1199
491
  timestamp: new Date(10).getTime() as Seconds,
1200
492
  });
1201
493
  let cell = cells[1];
1202
- expect(cell.status).toBe("queued");
1203
- expect(cell.lastCodeRun).toBe("mo.slider()");
1204
- expect(cell.edited).toBe(false);
1205
- expect(cell.runElapsedTimeMs).toBe(null);
1206
- expect(cell.runStartTimestamp).toBe(null);
1207
494
  expect(cell).toMatchSnapshot("queued message received");
1208
495
 
1209
496
  // Receive stale message
@@ -1212,105 +499,15 @@ describe("cell reducer", () => {
1212
499
  output: undefined,
1213
500
  console: null,
1214
501
  status: undefined,
1215
- stale_inputs: true,
1216
502
  timestamp: new Date(20).getTime() as Seconds,
1217
503
  });
1218
504
  cell = cells[1];
1219
- expect(cell.status).toBe("queued");
1220
- expect(cell.lastCodeRun).toBe("mo.slider()");
1221
- expect(cell.edited).toBe(false);
1222
- expect(cell.runElapsedTimeMs).toBe(null);
1223
- expect(cell.runStartTimestamp).toBe(null);
1224
505
  expect(cell).toMatchSnapshot("stale inputs received");
1225
506
  });
1226
507
 
1227
- it("can format code and update cell", () => {
1228
- const firstCellId = cells[0].id;
1229
- actions.updateCellCode({
1230
- cellId: firstCellId,
1231
- code: "import marimo as mo",
1232
- formattingChange: false,
1233
- });
1234
- let cell = cells[0];
1235
- expect(cell.status).toBe("idle");
1236
- expect(cell.lastCodeRun).toBe(null);
1237
- expect(cell.edited).toBe(true);
1238
-
1239
- // Run
1240
- actions.prepareForRun({
1241
- cellId: firstCellId,
1242
- });
1243
- actions.handleCellMessage({
1244
- cell_id: firstCellId,
1245
- output: {
1246
- channel: "output",
1247
- mimetype: "text/plain",
1248
- data: "ok",
1249
- timestamp: 0,
1250
- },
1251
- console: {
1252
- channel: "stdout",
1253
- mimetype: "text/plain",
1254
- data: "hello!",
1255
- timestamp: 0,
1256
- },
1257
- status: "idle",
1258
- stale_inputs: null,
1259
- timestamp: new Date(33).getTime() as Seconds,
1260
- });
1261
-
1262
- // Check steady state
1263
- cell = cells[0];
1264
- expect(cell.status).toBe("idle");
1265
- expect(cell.edited).toBe(false);
1266
- expect(cell.lastCodeRun).toBe("import marimo as mo");
1267
-
1268
- // Format code
1269
- actions.updateCellCode({
1270
- cellId: firstCellId,
1271
- code: "import marimo as mo",
1272
- formattingChange: true,
1273
- });
1274
- cell = cells[0];
1275
- expect(cell.status).toBe("idle");
1276
- expect(cell.lastCodeRun).toBe("import marimo as mo");
1277
- expect(cell.edited).toBe(false);
1278
- });
1279
-
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
508
  it("can run a stopped cell", () => {
1308
509
  // Update code of first cell
1309
- actions.updateCellCode({
1310
- cellId: firstCellId,
1311
- code: "mo.md('This has an ancestor that was stopped')",
1312
- formattingChange: false,
1313
- });
510
+ editCode(firstCellId, "mo.md('This has an ancestor that was stopped')");
1314
511
 
1315
512
  // Prepare for run
1316
513
  actions.prepareForRun({
@@ -1323,17 +520,9 @@ describe("cell reducer", () => {
1323
520
  output: undefined,
1324
521
  console: null,
1325
522
  status: "queued",
1326
- stale_inputs: null,
1327
523
  timestamp: new Date(10).getTime() as Seconds,
1328
524
  });
1329
525
  let cell = cells[0];
1330
- expect(cell.status).toBe("queued");
1331
- expect(cell.lastCodeRun).toBe(
1332
- "mo.md('This has an ancestor that was stopped')",
1333
- );
1334
- expect(cell.edited).toBe(false);
1335
- expect(cell.runElapsedTimeMs).toBe(null);
1336
- expect(cell.runStartTimestamp).toBe(null);
1337
526
  expect(cell).toMatchSnapshot("queued message received");
1338
527
 
1339
528
  // Receive idle message
@@ -1342,7 +531,6 @@ describe("cell reducer", () => {
1342
531
  output: undefined,
1343
532
  console: null,
1344
533
  status: "idle",
1345
- stale_inputs: null,
1346
534
  timestamp: new Date(20).getTime() as Seconds,
1347
535
  });
1348
536
  cell = cells[0];
@@ -1365,16 +553,13 @@ describe("cell reducer", () => {
1365
553
  },
1366
554
  console: null,
1367
555
  status: "idle",
1368
- stale_inputs: null,
1369
556
  timestamp: new Date(20).getTime() as Seconds,
1370
557
  });
1371
558
  cell = cells[0];
1372
- expect(cell.status).toBe("idle");
1373
559
  // oxlint-disable-next-line typescript/no-explicit-any no-unsafe-optional-chaining
1374
560
  expect((cell.output?.data as any)[0].msg).toBe(
1375
561
  "This cell wasn't run because an ancestor was stopped with `mo.stop`: ",
1376
562
  );
1377
- expect(cell.stopped).toBe(true);
1378
563
  expect(cell).toMatchSnapshot("ancestor stopped");
1379
564
 
1380
565
  // Receive queued message
@@ -1383,12 +568,9 @@ describe("cell reducer", () => {
1383
568
  output: undefined,
1384
569
  console: null,
1385
570
  status: "queued",
1386
- stale_inputs: null,
1387
571
  timestamp: new Date(30).getTime() as Seconds,
1388
572
  });
1389
573
  cell = cells[0];
1390
- expect(cell.status).toBe("queued");
1391
- expect(cell.stopped).toBe(true);
1392
574
  expect(cell).toMatchSnapshot("queued after stop");
1393
575
 
1394
576
  // Receive running message
@@ -1397,12 +579,9 @@ describe("cell reducer", () => {
1397
579
  output: undefined,
1398
580
  console: null,
1399
581
  status: "running",
1400
- stale_inputs: null,
1401
582
  timestamp: new Date(40).getTime() as Seconds,
1402
583
  });
1403
584
  cell = cells[0];
1404
- expect(cell.status).toBe("running");
1405
- expect(cell.stopped).toBe(false);
1406
585
  expect(cell.output).toBe(null);
1407
586
  expect(cell).toMatchSnapshot("running after stop");
1408
587
  });
@@ -1429,7 +608,6 @@ describe("cell reducer", () => {
1429
608
 
1430
609
  // Initial state
1431
610
  let cell = cells[0];
1432
- expect(cell.status).toBe("idle");
1433
611
  expect(cell.consoleOutputs).toEqual([]);
1434
612
  expect(cell).toMatchSnapshot("initial state");
1435
613
 
@@ -1438,7 +616,6 @@ describe("cell reducer", () => {
1438
616
  cellId: firstCellId,
1439
617
  });
1440
618
  cell = cells[0];
1441
- expect(cell.status).toBe("queued");
1442
619
  expect(cell.consoleOutputs).toEqual([]);
1443
620
  expect(cell).toMatchSnapshot("prepared for run");
1444
621
 
@@ -1448,11 +625,9 @@ describe("cell reducer", () => {
1448
625
  output: undefined,
1449
626
  console: null,
1450
627
  status: "queued",
1451
- stale_inputs: null,
1452
628
  timestamp: new Date(10).getTime() as Seconds,
1453
629
  });
1454
630
  cell = cells[0];
1455
- expect(cell.status).toBe("queued");
1456
631
  expect(cell).toMatchSnapshot("queued message received");
1457
632
 
1458
633
  // Receive running messages
@@ -1461,11 +636,9 @@ describe("cell reducer", () => {
1461
636
  output: undefined,
1462
637
  console: null,
1463
638
  status: "running",
1464
- stale_inputs: null,
1465
639
  timestamp: new Date(20).getTime() as Seconds,
1466
640
  });
1467
641
  cell = cells[0];
1468
- expect(cell.status).toBe("running");
1469
642
  expect(cell).toMatchSnapshot("running message received");
1470
643
 
1471
644
  // Add console
@@ -1474,16 +647,13 @@ describe("cell reducer", () => {
1474
647
  output: undefined,
1475
648
  console: STDOUT,
1476
649
  status: undefined,
1477
- stale_inputs: null,
1478
650
  timestamp: new Date(22).getTime() as Seconds,
1479
651
  });
1480
652
  cell = cells[0];
1481
- expect(cell.status).toBe("running");
1482
653
  expect(cell).toMatchSnapshot("stdout received");
1483
654
 
1484
655
  cell = cells[0];
1485
656
  expect(cell.consoleOutputs[0]).toMatchObject(STDOUT);
1486
- expect(cell.status).toBe("running");
1487
657
  expect(cell).toMatchSnapshot("stdout verified");
1488
658
 
1489
659
  // Ask via stdin
@@ -1492,13 +662,11 @@ describe("cell reducer", () => {
1492
662
  output: undefined,
1493
663
  console: STD_IN_1,
1494
664
  status: undefined,
1495
- stale_inputs: null,
1496
665
  timestamp: new Date(22).getTime() as Seconds,
1497
666
  });
1498
667
  cell = cells[0];
1499
668
  expect(cell.consoleOutputs[0]).toMatchObject(STDOUT);
1500
669
  expect(cell.consoleOutputs[1]).toMatchObject(STD_IN_1);
1501
- expect(cell.status).toBe("running");
1502
670
  expect(cell).toMatchSnapshot("stdin requested");
1503
671
 
1504
672
  // Response to stdin
@@ -1512,7 +680,6 @@ describe("cell reducer", () => {
1512
680
  ...STD_IN_1,
1513
681
  response: "Marimo!",
1514
682
  });
1515
- expect(cell.status).toBe("running");
1516
683
  expect(cell).toMatchSnapshot("stdin answered");
1517
684
 
1518
685
  // Ask via stdin, again
@@ -1521,7 +688,6 @@ describe("cell reducer", () => {
1521
688
  output: undefined,
1522
689
  console: STD_IN_2,
1523
690
  status: undefined,
1524
- stale_inputs: null,
1525
691
  timestamp: new Date(22).getTime() as Seconds,
1526
692
  });
1527
693
  cell = cells[0];
@@ -1530,7 +696,6 @@ describe("cell reducer", () => {
1530
696
  { ...STD_IN_1, response: "Marimo!" },
1531
697
  STD_IN_2,
1532
698
  ]);
1533
- expect(cell.status).toBe("running");
1534
699
  expect(cell).toMatchSnapshot("second stdin requested");
1535
700
 
1536
701
  // Interrupt, so we respond with ""
@@ -1544,11 +709,9 @@ describe("cell reducer", () => {
1544
709
  },
1545
710
  console: null,
1546
711
  status: "idle",
1547
- stale_inputs: null,
1548
712
  timestamp: new Date(61).getTime() as Seconds,
1549
713
  });
1550
714
  cell = cells[0];
1551
- expect(cell.status).toBe("idle");
1552
715
  expect(cell.consoleOutputs).toMatchObject([
1553
716
  STDOUT,
1554
717
  { ...STD_IN_1, response: "Marimo!" },
@@ -1568,7 +731,6 @@ describe("cell reducer", () => {
1568
731
  output: undefined,
1569
732
  console: prompt,
1570
733
  status: "running",
1571
- stale_inputs: null,
1572
734
  timestamp: 1 as Seconds,
1573
735
  });
1574
736
 
@@ -1579,7 +741,9 @@ describe("cell reducer", () => {
1579
741
  response: secret,
1580
742
  });
1581
743
 
1582
- expect(cells[0].consoleOutputs).toEqual([{ ...prompt, response: "" }]);
744
+ expect(cells[0].consoleOutputs).toEqual([
745
+ { ...prompt, id: 1, response: "" },
746
+ ]);
1583
747
  expect(JSON.stringify(cells[0].consoleOutputs)).not.toContain(secret);
1584
748
  });
1585
749
 
@@ -1598,7 +762,6 @@ describe("cell reducer", () => {
1598
762
  output: undefined,
1599
763
  console: null,
1600
764
  status: "running",
1601
- stale_inputs: null,
1602
765
  timestamp: new Date(20).getTime() as Seconds,
1603
766
  });
1604
767
  actions.handleCellMessage({
@@ -1606,7 +769,6 @@ describe("cell reducer", () => {
1606
769
  output: undefined,
1607
770
  console: STDOUT,
1608
771
  status: undefined,
1609
- stale_inputs: null,
1610
772
  timestamp: new Date(22).getTime() as Seconds,
1611
773
  });
1612
774
 
@@ -1640,7 +802,6 @@ describe("cell reducer", () => {
1640
802
 
1641
803
  // Initial state
1642
804
  let cell = cells[0];
1643
- expect(cell.status).toBe("idle");
1644
805
  expect(cell.consoleOutputs).toEqual([]);
1645
806
  expect(cell).toMatchSnapshot("initial state");
1646
807
 
@@ -1650,7 +811,6 @@ describe("cell reducer", () => {
1650
811
  output: undefined,
1651
812
  console: [OLD_STDOUT],
1652
813
  status: undefined,
1653
- stale_inputs: null,
1654
814
  timestamp: new Date(1).getTime() as Seconds,
1655
815
  });
1656
816
 
@@ -1659,7 +819,6 @@ describe("cell reducer", () => {
1659
819
  cellId: firstCellId,
1660
820
  });
1661
821
  cell = cells[0];
1662
- expect(cell.status).toBe("queued");
1663
822
  expect(cell.consoleOutputs).toMatchObject([OLD_STDOUT]); // Old stays there until it starts running
1664
823
  expect(cell).toMatchSnapshot("prepared for run");
1665
824
 
@@ -1669,11 +828,9 @@ describe("cell reducer", () => {
1669
828
  output: undefined,
1670
829
  console: null,
1671
830
  status: "queued",
1672
- stale_inputs: null,
1673
831
  timestamp: new Date(10).getTime() as Seconds,
1674
832
  });
1675
833
  cell = cells[0];
1676
- expect(cell.status).toBe("queued");
1677
834
  expect(cell.consoleOutputs).toMatchObject([OLD_STDOUT]); // Old stays there until it starts running
1678
835
  expect(cell).toMatchSnapshot("queued message received");
1679
836
 
@@ -1683,624 +840,50 @@ describe("cell reducer", () => {
1683
840
  output: undefined,
1684
841
  console: [], // Backend sends an empty array to clearu
1685
842
  status: "running",
1686
- stale_inputs: null,
1687
843
  timestamp: new Date(20).getTime() as Seconds,
1688
844
  });
1689
845
  cell = cells[0];
1690
- expect(cell.status).toBe("running");
1691
846
  expect(cell.consoleOutputs).toMatchObject([]);
1692
847
  expect(cell).toMatchSnapshot("console cleared while running");
1693
848
 
1694
849
  // Add console
1695
850
  actions.handleCellMessage({
1696
- cell_id: firstCellId,
1697
- output: undefined,
1698
- console: [STDOUT],
1699
- status: "idle",
1700
- stale_inputs: null,
1701
- timestamp: new Date(22).getTime() as Seconds,
1702
- });
1703
- cell = cells[0];
1704
- expect(cell.consoleOutputs).toMatchObject([STDOUT]);
1705
- expect(cell.status).toBe("idle");
1706
- expect(cell).toMatchSnapshot("console received while idle");
1707
- });
1708
-
1709
- it("can send a cell to the top", () => {
1710
- actions.createNewCell({ cellId: firstCellId, before: false });
1711
- actions.createNewCell({ cellId: cellId("1"), before: false });
1712
- actions.sendToTop({ cellId: cellId("2") });
1713
- expect(formatCells(state)).toMatchInlineSnapshot(`
1714
- "
1715
- [2] ''
1716
-
1717
- [0] ''
1718
-
1719
- [1] ''
1720
- "
1721
- `);
1722
- });
1723
-
1724
- it("can send a cell to the bottom", () => {
1725
- actions.createNewCell({ cellId: firstCellId, before: false });
1726
- actions.createNewCell({ cellId: cellId("1"), before: false });
1727
- actions.sendToBottom({ cellId: firstCellId });
1728
- expect(formatCells(state)).toMatchInlineSnapshot(`
1729
- "
1730
- [1] ''
1731
-
1732
- [2] ''
1733
-
1734
- [0] ''
1735
- "
1736
- `);
1737
- });
1738
-
1739
- it("can focus cells", () => {
1740
- actions.createNewCell({ cellId: firstCellId, before: false });
1741
- actions.createNewCell({ cellId: cellId("1"), before: false });
1742
-
1743
- actions.focusCell({ cellId: cellId("1"), where: "before" });
1744
- expect(focusAndScrollCellIntoView).toHaveBeenCalledWith(
1745
- expect.objectContaining({
1746
- cellId: cellId("0"),
1747
- }),
1748
- );
1749
-
1750
- actions.focusTopCell();
1751
- expect(scrollToTop).toHaveBeenCalled();
1752
-
1753
- actions.focusBottomCell();
1754
- expect(scrollToBottom).toHaveBeenCalled();
1755
- });
1756
-
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);
1768
-
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
- });
1950
-
1951
- it("can clear logs", () => {
1952
- state.cellLogs = [
1953
- {
1954
- level: "stderr",
1955
- message: "log1",
1956
- timestamp: 0,
1957
- source: { type: "cell", cellId: firstCellId },
1958
- },
1959
- {
1960
- level: "stderr",
1961
- message: "log1",
1962
- timestamp: 0,
1963
- source: { type: "cell", cellId: firstCellId },
1964
- },
1965
- ];
1966
- actions.clearLogs();
1967
- expect(state.cellLogs).toEqual([]);
1968
- });
1969
-
1970
- it("can collapse and expand cells", () => {
1971
- actions.createNewCell({ cellId: firstCellId, before: false });
1972
- actions.createNewCell({
1973
- cellId: cellId("1"),
1974
- before: false,
1975
- code: "# Header",
1976
- });
1977
- actions.createNewCell({
1978
- cellId: cellId("2"),
1979
- before: false,
1980
- code: "## Subheader",
1981
- });
1982
-
1983
- const id = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
1984
- state.cellRuntime[id] = {
1985
- ...state.cellRuntime[id],
1986
- outline: {
1987
- items: [{ name: "Header", level: 1, by: { id: "header" } }],
1988
- },
1989
- };
1990
-
1991
- actions.collapseCell({ cellId: id });
1992
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(id)).toBe(true);
1993
-
1994
- actions.expandCell({ cellId: id });
1995
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(id)).toBe(false);
1996
- });
1997
-
1998
- it("can collapse and expand all cells in multiple columns", () => {
1999
- actions.createNewCell({ cellId: firstCellId, before: false });
2000
- actions.createNewCell({
2001
- cellId: cellId("1"),
2002
- before: false,
2003
- code: "# First Column Header",
2004
- });
2005
- actions.createNewCell({
2006
- cellId: cellId("2"),
2007
- before: false,
2008
- code: "## First Column Subheader",
2009
- });
2010
-
2011
- actions.addColumnBreakpoint({ cellId: cellId("2") });
2012
-
2013
- actions.createNewCell({
2014
- cellId: cellId("3"),
2015
- before: false,
2016
- code: "# Second Column Header",
2017
- });
2018
- actions.createNewCell({
2019
- cellId: cellId("4"),
2020
- before: false,
2021
- code: "## Second Column Subheader",
2022
- });
2023
-
2024
- const firstColumnHeaderId = state.cellIds
2025
- .atOrThrow(FIRST_COLUMN)
2026
- .atOrThrow(1);
2027
- state.cellRuntime[firstColumnHeaderId] = {
2028
- ...state.cellRuntime[firstColumnHeaderId],
2029
- outline: {
2030
- items: [
2031
- { name: "First Column Header", level: 1, by: { id: "header" } },
2032
- ],
2033
- },
2034
- };
2035
-
2036
- const secondColumnHeaderId = state.cellIds
2037
- .atOrThrow(SECOND_COLUMN)
2038
- .atOrThrow(0);
2039
- state.cellRuntime[secondColumnHeaderId] = {
2040
- ...state.cellRuntime[secondColumnHeaderId],
2041
- outline: {
2042
- items: [
2043
- { name: "Second Column Header", level: 1, by: { id: "header" } },
2044
- ],
2045
- },
2046
- };
2047
-
2048
- actions.collapseAllCells();
2049
- expect(
2050
- state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(firstColumnHeaderId),
2051
- ).toBe(true);
2052
- expect(
2053
- state.cellIds.atOrThrow(SECOND_COLUMN).isCollapsed(secondColumnHeaderId),
2054
- ).toBe(true);
2055
-
2056
- actions.expandAllCells();
2057
- expect(
2058
- state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(firstColumnHeaderId),
2059
- ).toBe(false);
2060
- expect(
2061
- state.cellIds.atOrThrow(SECOND_COLUMN).isCollapsed(secondColumnHeaderId),
2062
- ).toBe(false);
2063
- });
2064
-
2065
- it("can collapse and expand nested cells in one call", () => {
2066
- actions.createNewCell({ cellId: firstCellId, before: false });
2067
- actions.createNewCell({
2068
- cellId: cellId("1"),
2069
- before: false,
2070
- code: "# Header",
2071
- });
2072
- actions.createNewCell({
2073
- cellId: cellId("2"),
2074
- before: false,
2075
- code: "## Subheader",
2076
- });
2077
- actions.createNewCell({
2078
- cellId: cellId("3"),
2079
- before: false,
2080
- code: "### Subsubheader",
2081
- });
2082
-
2083
- const headerId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
2084
- state.cellRuntime[headerId] = {
2085
- ...state.cellRuntime[headerId],
2086
- outline: {
2087
- items: [{ name: "Header", level: 1, by: { id: "header" } }],
2088
- },
2089
- };
2090
-
2091
- const subheaderId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(2);
2092
- state.cellRuntime[subheaderId] = {
2093
- ...state.cellRuntime[subheaderId],
2094
- outline: {
2095
- items: [{ name: "Subheader", level: 2, by: { id: "subheader" } }],
2096
- },
2097
- };
2098
-
2099
- // Check if both the parent and child are collapsed
2100
- actions.collapseAllCells();
2101
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(headerId)).toBe(
2102
- true,
2103
- );
2104
- actions.expandCell({ cellId: headerId });
2105
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(subheaderId)).toBe(
2106
- true,
2107
- );
2108
- actions.collapseCell({ cellId: headerId });
2109
-
2110
- // Check if both the parent and child are expanded
2111
- actions.expandAllCells();
2112
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(headerId)).toBe(
2113
- false,
2114
- );
2115
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(subheaderId)).toBe(
2116
- false,
2117
- );
2118
- });
2119
-
2120
- it("can collapse all cells when nested heading ranges end at the same cell", () => {
2121
- actions.createNewCell({ cellId: firstCellId, before: false });
2122
- actions.createNewCell({
2123
- cellId: cellId("1"),
2124
- before: false,
2125
- code: "# Header",
2126
- });
2127
- actions.createNewCell({
2128
- cellId: cellId("2"),
2129
- before: false,
2130
- code: "## Subheader",
2131
- });
2132
- actions.createNewCell({
2133
- cellId: cellId("3"),
2134
- before: false,
2135
- code: "### Subsubheader",
2136
- });
2137
-
2138
- const headerId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
2139
- state.cellRuntime[headerId] = {
2140
- ...state.cellRuntime[headerId],
2141
- outline: {
2142
- items: [{ name: "Header", level: 1, by: { id: "header" } }],
2143
- },
2144
- };
2145
-
2146
- const subheaderId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(2);
2147
- state.cellRuntime[subheaderId] = {
2148
- ...state.cellRuntime[subheaderId],
2149
- outline: {
2150
- items: [{ name: "Subheader", level: 2, by: { id: "subheader" } }],
2151
- },
2152
- };
2153
-
2154
- const subsubheaderId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(3);
2155
- state.cellRuntime[subsubheaderId] = {
2156
- ...state.cellRuntime[subsubheaderId],
2157
- outline: {
2158
- items: [{ name: "Subsubheader", level: 3, by: { id: "subsubheader" } }],
2159
- },
2160
- };
2161
-
2162
- // The header, subheader and subsubheader ranges all end at the last
2163
- // cell. Collapsing all used to throw "Node ... not found in tree" and
2164
- // leave the whole notebook unchanged.
2165
- actions.collapseAllCells();
2166
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(headerId)).toBe(
2167
- true,
2168
- );
2169
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).topLevelIds).toEqual([
2170
- firstCellId,
2171
- headerId,
2172
- ]);
2173
-
2174
- // Each level is nested under its parent, collapsed
2175
- actions.expandCell({ cellId: headerId });
2176
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(subheaderId)).toBe(
2177
- true,
2178
- );
2179
- actions.expandCell({ cellId: subheaderId });
2180
- expect(
2181
- state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(subsubheaderId),
2182
- ).toBe(true);
2183
- });
2184
-
2185
- 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({
2188
- cellId: cellId("1"),
2189
- before: false,
2190
- code: "# Header",
2191
- });
2192
- actions.createNewCell({
2193
- cellId: cellId("2"),
2194
- before: false,
2195
- code: "## Section A",
2196
- });
2197
- actions.createNewCell({
2198
- cellId: cellId("3"),
2199
- before: false,
2200
- code: "### Sub of A",
2201
- });
2202
- actions.createNewCell({
2203
- cellId: cellId("4"),
2204
- before: false,
2205
- code: "## Section B",
2206
- });
2207
-
2208
- const headerId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
2209
- state.cellRuntime[headerId] = {
2210
- ...state.cellRuntime[headerId],
2211
- outline: {
2212
- items: [{ name: "Header", level: 1, by: { id: "header" } }],
2213
- },
2214
- };
2215
-
2216
- const sectionAId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(2);
2217
- state.cellRuntime[sectionAId] = {
2218
- ...state.cellRuntime[sectionAId],
2219
- outline: {
2220
- items: [{ name: "Section A", level: 2, by: { id: "section-a" } }],
2221
- },
2222
- };
2223
-
2224
- const subsectionId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(3);
2225
- state.cellRuntime[subsectionId] = {
2226
- ...state.cellRuntime[subsectionId],
2227
- outline: {
2228
- items: [{ name: "Sub of A", level: 3, by: { id: "sub-of-a" } }],
2229
- },
2230
- };
2231
-
2232
- const sectionBId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(4);
2233
- state.cellRuntime[sectionBId] = {
2234
- ...state.cellRuntime[sectionBId],
2235
- outline: {
2236
- items: [{ name: "Section B", level: 2, by: { id: "section-b" } }],
2237
- },
2238
- };
2239
-
2240
- actions.collapseAllCells();
2241
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(headerId)).toBe(
2242
- true,
2243
- );
2244
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).topLevelIds).toEqual([
2245
- firstCellId,
2246
- headerId,
2247
- ]);
2248
-
2249
- // Both sections sit under the header, collapsed, with the subsection
2250
- // nested under section A
2251
- actions.expandCell({ cellId: headerId });
2252
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(sectionAId)).toBe(
2253
- true,
2254
- );
2255
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(sectionBId)).toBe(
2256
- true,
2257
- );
2258
- actions.expandCell({ cellId: sectionAId });
2259
- expect(
2260
- state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(subsectionId),
2261
- ).toBe(true);
851
+ cell_id: firstCellId,
852
+ output: undefined,
853
+ console: [STDOUT],
854
+ status: "idle",
855
+ timestamp: new Date(22).getTime() as Seconds,
856
+ });
857
+ cell = cells[0];
858
+ expect(cell.consoleOutputs).toMatchObject([STDOUT]);
859
+ expect(cell).toMatchSnapshot("console received while idle");
2262
860
  });
2263
861
 
2264
- it("can show hidden cells", () => {
2265
- actions.createNewCell({ cellId: firstCellId, before: false });
2266
- actions.createNewCell({ cellId: cellId("1"), before: false });
2267
- actions.collapseCell({ cellId: firstCellId });
862
+ it("can fold and unfold all cells", () => {
863
+ actions.foldAll();
864
+ expect(foldAllBulk).toHaveBeenCalled();
2268
865
 
2269
- actions.showCellIfHidden({ cellId: cellId("1") });
2270
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(firstCellId)).toBe(
2271
- false,
2272
- );
866
+ actions.unfoldAll();
867
+ expect(unfoldAllBulk).toHaveBeenCalled();
2273
868
  });
2274
869
 
2275
- it("can split and undo split cells", () => {
2276
- actions.createNewCell({
2277
- cellId: firstCellId,
2278
- before: false,
2279
- code: "line1\nline2",
2280
- });
2281
- const nextCellId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
2282
-
2283
- const originalCellCount = state.cellIds.atOrThrow(FIRST_COLUMN).length;
2284
- // Move cursor to the second line
2285
- const editor = state.cellHandles[nextCellId].current?.editorView;
2286
- if (!editor) {
2287
- throw new Error("Editor not found");
2288
- }
2289
- editor.dispatch({ selection: { anchor: 5, head: 5 } });
2290
- actions.splitCell({ cellId: nextCellId });
2291
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).length).toBe(
2292
- originalCellCount + 1,
2293
- );
2294
- expect(state.cellData[nextCellId].code).toBe("line1");
2295
- expect(
2296
- state.cellData[state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(2)].code,
2297
- ).toBe("line2");
2298
-
2299
- actions.undoSplitCell({ cellId: nextCellId, snapshot: "line1\nline2" });
2300
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).length).toBe(
2301
- originalCellCount,
2302
- );
2303
- expect(state.cellData[nextCellId].code).toBe("line1\nline2");
870
+ it("can clear logs", () => {
871
+ state.cellLogs = [
872
+ {
873
+ level: "stderr",
874
+ message: "log1",
875
+ timestamp: 0,
876
+ source: { type: "cell", cellId: firstCellId },
877
+ },
878
+ {
879
+ level: "stderr",
880
+ message: "log1",
881
+ timestamp: 0,
882
+ source: { type: "cell", cellId: firstCellId },
883
+ },
884
+ ];
885
+ actions.clearLogs();
886
+ expect(state.cellLogs).toEqual([]);
2304
887
  });
2305
888
 
2306
889
  it("can handle multiple console outputs", () => {
@@ -2322,7 +905,6 @@ describe("cell reducer", () => {
2322
905
  output: undefined,
2323
906
  console: [STDOUT1, STDOUT2],
2324
907
  status: "running",
2325
- stale_inputs: null,
2326
908
  timestamp: new Date(20).getTime() as Seconds,
2327
909
  });
2328
910
 
@@ -2339,407 +921,6 @@ describe("cell reducer", () => {
2339
921
  `);
2340
922
  });
2341
923
 
2342
- 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 });
2346
-
2347
- expect(state.cellIds.getColumns().length).toBe(1);
2348
- expect(formatCells(state)).toMatchInlineSnapshot(`
2349
- "
2350
- [0] ''
2351
-
2352
- [1] ''
2353
-
2354
- [2] ''
2355
-
2356
- [3] ''
2357
- "
2358
- `);
2359
-
2360
- actions.addColumnBreakpoint({ cellId: cellId("2") });
2361
-
2362
- expect(state.cellIds.getColumns().length).toBe(2);
2363
- expect(formatCells(state)).toMatchInlineSnapshot(`
2364
- "
2365
- > col 0
2366
- [0] ''
2367
-
2368
- [1] ''
2369
-
2370
- > col 1
2371
- [2] ''
2372
-
2373
- [3] ''
2374
- "
2375
- `);
2376
-
2377
- // Check that the cells are in the correct columns
2378
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
2379
- cellId("0"),
2380
- cellId("1"),
2381
- ]);
2382
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([
2383
- cellId("2"),
2384
- cellId("3"),
2385
- ]);
2386
- });
2387
-
2388
- it("cannot add a column breakpoint before the first cell", () => {
2389
- expect(state.cellIds.getColumns().length).toBe(1);
2390
- actions.createNewCell({ cellId: firstCellId, before: false });
2391
- actions.createNewCell({ cellId: cellId("1"), before: false });
2392
- actions.addColumnBreakpoint({ cellId: firstCellId });
2393
- expect(state.cellIds.getColumns().length).toBe(1);
2394
- });
2395
-
2396
- 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 });
2400
- actions.addColumnBreakpoint({ cellId: cellId("2") });
2401
-
2402
- expect(state.cellIds.getColumns().length).toBe(2);
2403
- expect(formatCells(state)).toMatchInlineSnapshot(`
2404
- "
2405
- > col 0
2406
- [0] ''
2407
-
2408
- [1] ''
2409
-
2410
- > col 1
2411
- [2] ''
2412
-
2413
- [3] ''
2414
- "
2415
- `);
2416
-
2417
- const columnId = state.cellIds.atOrThrow(0).id;
2418
- actions.deleteColumn({ columnId: columnId });
2419
-
2420
- expect(state.cellIds.getColumns().length).toBe(1);
2421
- expect(formatCells(state)).toMatchInlineSnapshot(`
2422
- "
2423
- [2] ''
2424
-
2425
- [3] ''
2426
-
2427
- [0] ''
2428
-
2429
- [1] ''
2430
- "
2431
- `);
2432
-
2433
- // Check that all cells are now in the remaining column
2434
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
2435
- "2",
2436
- "3",
2437
- "0",
2438
- "1",
2439
- ]);
2440
- });
2441
-
2442
- it("deleting the last column does nothing", () => {
2443
- actions.createNewCell({ cellId: firstCellId, before: false });
2444
- actions.createNewCell({ cellId: cellId("1"), before: false });
2445
-
2446
- const initialState = { ...state };
2447
-
2448
- actions.deleteColumn({ columnId: initialState.cellIds.atOrThrow(0).id });
2449
-
2450
- // State should not change
2451
- expect(state).toEqual(initialState);
2452
- });
2453
-
2454
- 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 });
2458
-
2459
- expect(formatCells(state)).toMatchInlineSnapshot(`
2460
- "
2461
- [0] ''
2462
-
2463
- [1] ''
2464
-
2465
- [2] ''
2466
-
2467
- [3] ''
2468
- "
2469
- `);
2470
-
2471
- actions.dropCellOverCell({
2472
- cellId: cellId("0"),
2473
- overCellId: cellId("3"),
2474
- });
2475
-
2476
- expect(formatCells(state)).toMatchInlineSnapshot(`
2477
- "
2478
- [1] ''
2479
-
2480
- [2] ''
2481
-
2482
- [3] ''
2483
-
2484
- [0] ''
2485
- "
2486
- `);
2487
- });
2488
-
2489
- it("can drop a cell over a new column", () => {
2490
- actions.createNewCell({ cellId: firstCellId, before: false });
2491
- actions.createNewCell({ cellId: cellId("1"), before: false });
2492
-
2493
- expect(state.cellIds.getColumns().length).toBe(1);
2494
- expect(formatCells(state)).toMatchInlineSnapshot(`
2495
- "
2496
- [0] ''
2497
-
2498
- [1] ''
2499
-
2500
- [2] ''
2501
- "
2502
- `);
2503
-
2504
- actions.dropOverNewColumn({ cellId: cellId("1") });
2505
-
2506
- expect(state.cellIds.getColumns().length).toBe(2);
2507
- expect(formatCells(state)).toMatchInlineSnapshot(`
2508
- "
2509
- > col 0
2510
- [0] ''
2511
-
2512
- [2] ''
2513
-
2514
- > col 1
2515
- [1] ''
2516
- "
2517
- `);
2518
-
2519
- // Check that the cells are in the correct columns
2520
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
2521
- cellId("0"),
2522
- cellId("2"),
2523
- ]);
2524
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([cellId("1")]);
2525
- });
2526
-
2527
- 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 });
2531
- actions.addColumnBreakpoint({ cellId: cellId("2") });
2532
-
2533
- expect(state.cellIds.getColumns().length).toBe(2);
2534
- expect(formatCells(state)).toMatchInlineSnapshot(`
2535
- "
2536
- > col 0
2537
- [0] ''
2538
-
2539
- [1] ''
2540
-
2541
- > col 1
2542
- [2] ''
2543
-
2544
- [3] ''
2545
- "
2546
- `);
2547
-
2548
- const columnId0 = state.cellIds.atOrThrow(0).id;
2549
- const columnId1 = state.cellIds.atOrThrow(1).id;
2550
- expect(columnId0).not.toBe(columnId1);
2551
- actions.moveColumn({ column: columnId1, overColumn: columnId0 });
2552
-
2553
- expect(state.cellIds.getColumns().length).toBe(2);
2554
- expect(formatCells(state)).toMatchInlineSnapshot(`
2555
- "
2556
- > col 0
2557
- [2] ''
2558
-
2559
- [3] ''
2560
-
2561
- > col 1
2562
- [0] ''
2563
-
2564
- [1] ''
2565
- "
2566
- `);
2567
-
2568
- // Check that the columns have swapped positions
2569
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
2570
- cellId("2"),
2571
- cellId("3"),
2572
- ]);
2573
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([
2574
- cellId("0"),
2575
- cellId("1"),
2576
- ]);
2577
- });
2578
-
2579
- it("can compact columns", () => {
2580
- // 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 });
2583
- actions.addColumnBreakpoint({ cellId: cellId("1") });
2584
- actions.addColumnBreakpoint({ cellId: cellId("2") });
2585
- actions.dropOverNewColumn({ cellId: cellId("2") });
2586
-
2587
- expect(state.cellIds.getColumns().length).toBe(4);
2588
- expect(formatCells(state)).toMatchInlineSnapshot(`
2589
- "
2590
- > col 0
2591
- [0] ''
2592
-
2593
- > col 1
2594
- [1] ''
2595
-
2596
- > col 2
2597
-
2598
-
2599
- > col 3
2600
- [2] ''
2601
- "
2602
- `);
2603
-
2604
- // Check initial column structure
2605
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([cellId("0")]);
2606
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([cellId("1")]);
2607
- expect(state.cellIds.getColumns()[2].topLevelIds).toEqual([]);
2608
- expect(state.cellIds.getColumns()[3].topLevelIds).toEqual([cellId("2")]);
2609
-
2610
- // Compact columns
2611
- actions.compactColumns();
2612
-
2613
- expect(state.cellIds.getColumns().length).toBe(3);
2614
- expect(formatCells(state)).toMatchInlineSnapshot(`
2615
- "
2616
- > col 0
2617
- [0] ''
2618
-
2619
- > col 1
2620
- [1] ''
2621
-
2622
- > col 2
2623
- [2] ''
2624
- "
2625
- `);
2626
-
2627
- // Check compacted column structure
2628
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([cellId("0")]);
2629
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([cellId("1")]);
2630
- expect(state.cellIds.getColumns()[2].topLevelIds).toEqual([cellId("2")]);
2631
- });
2632
-
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
924
  it("can clear output of a single cell", () => {
2744
925
  // Set up initial state with output
2745
926
  actions.handleCellMessage({
@@ -2757,7 +938,6 @@ describe("cell reducer", () => {
2757
938
  timestamp: 0,
2758
939
  },
2759
940
  status: "idle",
2760
- stale_inputs: null,
2761
941
  timestamp: new Date(33).getTime() as Seconds,
2762
942
  });
2763
943
 
@@ -2792,7 +972,6 @@ describe("cell reducer", () => {
2792
972
  timestamp: 0,
2793
973
  },
2794
974
  status: "idle",
2795
- stale_inputs: null,
2796
975
  timestamp: new Date(33).getTime() as Seconds,
2797
976
  });
2798
977
 
@@ -2806,7 +985,6 @@ describe("cell reducer", () => {
2806
985
  timestamp: 0,
2807
986
  },
2808
987
  status: "idle",
2809
- stale_inputs: null,
2810
988
  timestamp: new Date(34).getTime() as Seconds,
2811
989
  });
2812
990
 
@@ -2828,8 +1006,8 @@ describe("cell reducer", () => {
2828
1006
 
2829
1007
  it("can clear output of all cells", () => {
2830
1008
  // Create multiple cells with output
2831
- actions.createNewCell({ cellId: firstCellId, before: false });
2832
- const secondCellId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
1009
+ seedCell({ cellId: firstCellId, before: false });
1010
+ const secondCellId = state.cellIds[1];
2833
1011
 
2834
1012
  // Add output to both cells
2835
1013
  const outputMessage = {
@@ -2846,7 +1024,6 @@ describe("cell reducer", () => {
2846
1024
  timestamp: 0,
2847
1025
  },
2848
1026
  status: "idle",
2849
- stale_inputs: null,
2850
1027
  timestamp: new Date(33).getTime() as Seconds,
2851
1028
  } as const;
2852
1029
 
@@ -2857,139 +1034,27 @@ describe("cell reducer", () => {
2857
1034
  actions.handleCellMessage({
2858
1035
  ...outputMessage,
2859
1036
  cell_id: secondCellId,
2860
- });
2861
-
2862
- // Verify initial state has output
2863
- expect(state.cellRuntime[firstCellId].output).not.toBeNull();
2864
- expect(state.cellRuntime[firstCellId].consoleOutputs.length).toBe(1);
2865
- expect(state.cellRuntime[secondCellId].output).not.toBeNull();
2866
- expect(state.cellRuntime[secondCellId].consoleOutputs.length).toBe(1);
2867
-
2868
- // Clear all outputs
2869
- actions.clearAllCellOutputs();
2870
-
2871
- // Verify all outputs are cleared
2872
- expect(state.cellRuntime[firstCellId].output).toBeNull();
2873
- expect(state.cellRuntime[firstCellId].consoleOutputs).toEqual([]);
2874
- expect(state.cellRuntime[secondCellId].output).toBeNull();
2875
- expect(state.cellRuntime[secondCellId].consoleOutputs).toEqual([]);
2876
- });
2877
-
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" });
1037
+ });
2975
1038
 
2976
- // Delete the setup cell
2977
- actions.deleteCell({ cellId: SETUP_CELL_ID });
1039
+ // Verify initial state has output
1040
+ expect(state.cellRuntime[firstCellId].output).not.toBeNull();
1041
+ expect(state.cellRuntime[firstCellId].consoleOutputs.length).toBe(1);
1042
+ expect(state.cellRuntime[secondCellId].output).not.toBeNull();
1043
+ expect(state.cellRuntime[secondCellId].consoleOutputs.length).toBe(1);
2978
1044
 
2979
- // Create a new setup cell
2980
- actions.addSetupCellIfDoesntExist({ code: "# New code" });
1045
+ // Clear all outputs
1046
+ actions.clearAllCellOutputs();
2981
1047
 
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);
1048
+ // Verify all outputs are cleared
1049
+ expect(state.cellRuntime[firstCellId].output).toBeNull();
1050
+ expect(state.cellRuntime[firstCellId].consoleOutputs).toEqual([]);
1051
+ expect(state.cellRuntime[secondCellId].output).toBeNull();
1052
+ expect(state.cellRuntime[secondCellId].consoleOutputs).toEqual([]);
2988
1053
  });
2989
1054
 
2990
1055
  it("can clear all outputs", () => {
2991
1056
  // Add a cell and give it output
2992
- actions.createNewCell({
1057
+ seedCell({
2993
1058
  cellId: firstCellId,
2994
1059
  before: false,
2995
1060
  });
@@ -3017,522 +1082,13 @@ describe("cell reducer", () => {
3017
1082
  expect(state.cellRuntime[cell2Id].output).toBeNull();
3018
1083
  });
3019
1084
 
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
- describe("untouched cells functionality", () => {
3138
- it("starts with empty untouchedNewCells set", () => {
3139
- expect(state.untouchedNewCells).toEqual(new Set());
3140
- });
3141
-
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
- it("can mark a cell as touched", () => {
3214
- // Create a cell with hideCode
3215
- actions.createNewCell({
3216
- cellId: "__end__",
3217
- before: false,
3218
- hideCode: true,
3219
- });
3220
-
3221
- const newCellId =
3222
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3223
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3224
-
3225
- // Mark it as touched
3226
- actions.markTouched({ cellId: newCellId });
3227
-
3228
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3229
- });
3230
-
3231
- it("markTouched is idempotent", () => {
3232
- // Create a cell with hideCode
3233
- actions.createNewCell({
3234
- cellId: "__end__",
3235
- before: false,
3236
- hideCode: true,
3237
- });
3238
-
3239
- const newCellId =
3240
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3241
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3242
-
3243
- // Mark it as touched multiple times
3244
- actions.markTouched({ cellId: newCellId });
3245
- actions.markTouched({ cellId: newCellId });
3246
- actions.markTouched({ cellId: newCellId });
3247
-
3248
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3249
- });
3250
-
3251
- it("can mark a non-existent cell as touched without error", () => {
3252
- const nonExistentCellId = cellId("non-existent");
3253
-
3254
- expect(() => {
3255
- actions.markTouched({ cellId: nonExistentCellId });
3256
- }).not.toThrow();
3257
-
3258
- expect(state.untouchedNewCells.has(nonExistentCellId)).toBe(false);
3259
- });
3260
-
3261
- it("can handle multiple untouched cells", () => {
3262
- // Create multiple cells with hideCode
3263
- actions.createNewCell({
3264
- cellId: "__end__",
3265
- before: false,
3266
- hideCode: true,
3267
- });
3268
- actions.createNewCell({
3269
- cellId: "__end__",
3270
- before: false,
3271
- hideCode: true,
3272
- });
3273
- actions.createNewCell({
3274
- cellId: "__end__",
3275
- before: false,
3276
- hideCode: false,
3277
- });
3278
-
3279
- const cellIds = state.cellIds.inOrderIds;
3280
- const cell1Id = cellIds[cellIds.length - 3];
3281
- const cell2Id = cellIds[cellIds.length - 2];
3282
- const cell3Id = cellIds[cellIds.length - 1];
3283
-
3284
- expect(state.untouchedNewCells.has(cell1Id)).toBe(true);
3285
- expect(state.untouchedNewCells.has(cell2Id)).toBe(true);
3286
- expect(state.untouchedNewCells.has(cell3Id)).toBe(false);
3287
-
3288
- // Mark one as touched
3289
- actions.markTouched({ cellId: cell1Id });
3290
-
3291
- expect(state.untouchedNewCells.has(cell1Id)).toBe(false);
3292
- expect(state.untouchedNewCells.has(cell2Id)).toBe(true);
3293
- expect(state.untouchedNewCells.has(cell3Id)).toBe(false);
3294
- });
3295
-
3296
- it("preserves untouched state when cells are moved", () => {
3297
- // Create cells with hideCode
3298
- actions.createNewCell({
3299
- cellId: "__end__",
3300
- before: false,
3301
- hideCode: true,
3302
- });
3303
- actions.createNewCell({
3304
- cellId: "__end__",
3305
- before: false,
3306
- hideCode: true,
3307
- });
3308
-
3309
- const cellIds = state.cellIds.inOrderIds;
3310
- const cell1Id = cellIds[cellIds.length - 2];
3311
- const cell2Id = cellIds[cellIds.length - 1];
3312
-
3313
- expect(state.untouchedNewCells.has(cell1Id)).toBe(true);
3314
- expect(state.untouchedNewCells.has(cell2Id)).toBe(true);
3315
-
3316
- // Move cell1 to the end
3317
- actions.moveCell({
3318
- cellId: cell1Id,
3319
- before: false,
3320
- });
3321
-
3322
- // Both cells should still be untouched
3323
- expect(state.untouchedNewCells.has(cell1Id)).toBe(true);
3324
- expect(state.untouchedNewCells.has(cell2Id)).toBe(true);
3325
- });
3326
-
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
- it("untouched cell behavior with hide_code config", () => {
3377
- // Create a cell with hideCode and hide_code config
3378
- actions.createNewCell({
3379
- cellId: "__end__",
3380
- before: false,
3381
- hideCode: true,
3382
- });
3383
-
3384
- const newCellId =
3385
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3386
-
3387
- // Update the cell config to hide code
3388
- actions.updateCellConfig({
3389
- cellId: newCellId,
3390
- config: { hide_code: true },
3391
- });
3392
-
3393
- // Cell should be untouched and have hide_code config
3394
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3395
- expect(state.cellData[newCellId].config.hide_code).toBe(true);
3396
-
3397
- // Code should not be hidden because cell is untouched
3398
- expect(exportedForTesting.isCellCodeHidden(state, newCellId)).toBe(false);
3399
-
3400
- // Mark as touched
3401
- actions.markTouched({ cellId: newCellId });
3402
-
3403
- // Now code should be hidden
3404
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3405
- expect(exportedForTesting.isCellCodeHidden(state, newCellId)).toBe(true);
3406
- });
3407
-
3408
- it("can mark an existing cell as untouched", () => {
3409
- // Create a cell without hideCode (not in untouchedNewCells)
3410
- actions.createNewCell({
3411
- cellId: "__end__",
3412
- before: false,
3413
- hideCode: false,
3414
- });
3415
-
3416
- const newCellId =
3417
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3418
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3419
-
3420
- // Mark it as untouched
3421
- actions.markUntouched({ cellId: newCellId });
3422
-
3423
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3424
- });
3425
-
3426
- it("markUntouched is idempotent", () => {
3427
- // Create a cell without hideCode
3428
- actions.createNewCell({
3429
- cellId: "__end__",
3430
- before: false,
3431
- hideCode: false,
3432
- });
3433
-
3434
- const newCellId =
3435
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3436
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3437
-
3438
- // Mark as untouched multiple times
3439
- actions.markUntouched({ cellId: newCellId });
3440
- actions.markUntouched({ cellId: newCellId });
3441
- actions.markUntouched({ cellId: newCellId });
3442
-
3443
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3444
- });
3445
-
3446
- it("markUntouched does not affect already untouched cells", () => {
3447
- // Create a cell with hideCode (already in untouchedNewCells)
3448
- actions.createNewCell({
3449
- cellId: "__end__",
3450
- before: false,
3451
- hideCode: true,
3452
- });
3453
-
3454
- const newCellId =
3455
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3456
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3457
-
3458
- // Calling markUntouched should not change anything
3459
- actions.markUntouched({ cellId: newCellId });
3460
-
3461
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3462
- });
3463
-
3464
- it("markTouched and markUntouched can toggle cell state", () => {
3465
- // Create a cell without hideCode
3466
- actions.createNewCell({
3467
- cellId: "__end__",
3468
- before: false,
3469
- hideCode: false,
3470
- });
3471
-
3472
- const newCellId =
3473
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3474
-
3475
- // Initially not untouched
3476
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3477
-
3478
- // Mark as untouched
3479
- actions.markUntouched({ cellId: newCellId });
3480
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3481
-
3482
- // Mark as touched
3483
- actions.markTouched({ cellId: newCellId });
3484
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3485
-
3486
- // Mark as untouched again
3487
- actions.markUntouched({ cellId: newCellId });
3488
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3489
- });
3490
-
3491
- it("markUntouched works for markdown cell conversion scenario", () => {
3492
- // Simulates converting a Python cell to Markdown
3493
- // 1. Create a regular cell (no hideCode)
3494
- actions.createNewCell({
3495
- cellId: "__end__",
3496
- before: false,
3497
- hideCode: false,
3498
- });
3499
-
3500
- const cellId =
3501
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3502
-
3503
- // Cell starts without hide_code and not in untouchedNewCells
3504
- expect(state.cellData[cellId].config.hide_code).toBe(false);
3505
- expect(state.untouchedNewCells.has(cellId)).toBe(false);
3506
- expect(exportedForTesting.isCellCodeHidden(state, cellId)).toBe(false);
3507
-
3508
- // 2. Convert to markdown: set hide_code and mark as untouched
3509
- actions.updateCellConfig({
3510
- cellId,
3511
- config: { hide_code: true },
3512
- });
3513
- actions.markUntouched({ cellId });
3514
-
3515
- // Code should NOT be hidden because cell is untouched (user can edit)
3516
- expect(state.cellData[cellId].config.hide_code).toBe(true);
3517
- expect(state.untouchedNewCells.has(cellId)).toBe(true);
3518
- expect(exportedForTesting.isCellCodeHidden(state, cellId)).toBe(false);
3519
-
3520
- // 3. User blurs the cell (markTouched)
3521
- actions.markTouched({ cellId });
3522
-
3523
- // Now code should be hidden
3524
- expect(state.untouchedNewCells.has(cellId)).toBe(false);
3525
- expect(exportedForTesting.isCellCodeHidden(state, cellId)).toBe(true);
3526
- });
3527
- });
3528
-
3529
1085
  describe("releaseCellAtoms", () => {
3530
1086
  it("atom families cache atoms until explicitly removed", () => {
3531
1087
  const { cellDataAtom, cellRuntimeAtom, cellHandleAtom } =
3532
1088
  exportedForTesting;
3533
1089
 
3534
- actions.createNewCell({ cellId: firstCellId, before: false });
3535
- const newCellId = state.cellIds.inOrderIds[1];
1090
+ seedCell({ cellId: firstCellId, before: false });
1091
+ const newCellId = state.cellIds[1];
3536
1092
 
3537
1093
  const dataAtom1 = cellDataAtom(newCellId);
3538
1094
  const runtimeAtom1 = cellRuntimeAtom(newCellId);
@@ -3547,116 +1103,6 @@ describe("cell reducer", () => {
3547
1103
  expect(runtimeAtom2).toBe(runtimeAtom1);
3548
1104
  expect(handleAtom2).toBe(handleAtom1);
3549
1105
  });
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
- });
3576
- });
3577
-
3578
- describe("isCellCodeHidden", () => {
3579
- const state = initialNotebookState();
3580
- const firstCellId = state.cellIds.inOrderIds[0];
3581
-
3582
- it("returns false when hide_code is false and cell is not untouched", () => {
3583
- const testCellId = cellId("test-cell");
3584
- const testState: NotebookState = {
3585
- ...state,
3586
- cellData: {
3587
- ...state.cellData,
3588
- [testCellId]: {
3589
- ...state.cellData[firstCellId],
3590
- id: testCellId,
3591
- config: { hide_code: false, disabled: false, column: null },
3592
- },
3593
- },
3594
- untouchedNewCells: new Set(),
3595
- };
3596
-
3597
- expect(exportedForTesting.isCellCodeHidden(testState, testCellId)).toBe(
3598
- false,
3599
- );
3600
- });
3601
-
3602
- it("returns true when hide_code is true and cell is not untouched", () => {
3603
- const testCellId = cellId("test-cell");
3604
- const testState: NotebookState = {
3605
- ...state,
3606
- cellData: {
3607
- ...state.cellData,
3608
- [testCellId]: {
3609
- ...state.cellData[firstCellId],
3610
- id: testCellId,
3611
- config: { hide_code: true, disabled: false, column: null },
3612
- },
3613
- },
3614
- untouchedNewCells: new Set(),
3615
- };
3616
-
3617
- expect(exportedForTesting.isCellCodeHidden(testState, testCellId)).toBe(
3618
- true,
3619
- );
3620
- });
3621
-
3622
- it("returns false when hide_code is true but cell is untouched", () => {
3623
- const testCellId = cellId("test-cell");
3624
- const testState: NotebookState = {
3625
- ...state,
3626
- cellData: {
3627
- ...state.cellData,
3628
- [testCellId]: {
3629
- ...state.cellData[firstCellId],
3630
- id: testCellId,
3631
- config: { hide_code: true, disabled: false, column: null },
3632
- },
3633
- },
3634
- untouchedNewCells: new Set([testCellId]),
3635
- };
3636
-
3637
- expect(exportedForTesting.isCellCodeHidden(testState, testCellId)).toBe(
3638
- false,
3639
- );
3640
- });
3641
-
3642
- it("returns false when hide_code is false and cell is untouched", () => {
3643
- const testCellId = cellId("test-cell");
3644
- const testState: NotebookState = {
3645
- ...state,
3646
- cellData: {
3647
- ...state.cellData,
3648
- [testCellId]: {
3649
- ...state.cellData[firstCellId],
3650
- id: testCellId,
3651
- config: { hide_code: false, disabled: false, column: null },
3652
- },
3653
- },
3654
- untouchedNewCells: new Set([testCellId]),
3655
- };
3656
-
3657
- expect(exportedForTesting.isCellCodeHidden(testState, testCellId)).toBe(
3658
- false,
3659
- );
3660
1106
  });
3661
1107
  });
3662
1108
 
@@ -3839,11 +1285,11 @@ describe("createTracebackInfoAtom", () => {
3839
1285
  data: [{ type: "syntax", msg: "Syntax error", lineno: 1 }],
3840
1286
  mimetype: "application/vnd.marimo+error",
3841
1287
  },
3842
- status: "queued",
3843
1288
  },
3844
1289
  },
3845
1290
  }),
3846
1291
  );
1292
+ MockExecutions.seed(store, [MockExecutions.queued(cellId("cell1"))]);
3847
1293
 
3848
1294
  const tracebackAtom = exportedForTesting.createTracebackInfoAtom(
3849
1295
  cellId("cell1"),
@@ -3871,11 +1317,11 @@ describe("createTracebackInfoAtom", () => {
3871
1317
  data: [{ type: "syntax", msg: "Syntax error", lineno: 1 }],
3872
1318
  mimetype: "application/vnd.marimo+error",
3873
1319
  },
3874
- status: "running",
3875
1320
  },
3876
1321
  },
3877
1322
  }),
3878
1323
  );
1324
+ MockExecutions.seed(store, [MockExecutions.running(cellId("cell1"))]);
3879
1325
 
3880
1326
  const tracebackAtom = exportedForTesting.createTracebackInfoAtom(
3881
1327
  cellId("cell1"),
@@ -3886,31 +1332,9 @@ describe("createTracebackInfoAtom", () => {
3886
1332
  });
3887
1333
  });
3888
1334
 
3889
- describe("setCells snapshot preservation", () => {
1335
+ describe("kernel-ready", () => {
3890
1336
  const CELL_A = cellId("A");
3891
1337
  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
1338
 
3915
1339
  const hydratedState = () =>
3916
1340
  MockNotebook.notebookState({
@@ -3940,44 +1364,74 @@ describe("setCells snapshot preservation", () => {
3940
1364
  },
3941
1365
  });
3942
1366
 
3943
- beforeEach(async () => {
3944
- const { isWasm } = await import("@/core/wasm/utils");
3945
- vi.mocked(isWasm).mockReturnValue(true);
3946
- });
3947
-
3948
- it("preserves hydrated output in WASM", () => {
1367
+ it("resetCellRuntimes starts every cell's runtime over", () => {
3949
1368
  const next = exportedForTesting.reducer(hydratedState(), {
3950
- type: "setCells",
3951
- payload: newCells,
1369
+ type: "resetCellRuntimes",
1370
+ payload: undefined,
3952
1371
  });
3953
1372
 
3954
- expect(next.cellRuntime[CELL_A].output).toMatchObject({
3955
- data: "hydrated-A",
1373
+ expect(next.cellRuntime[CELL_A].output).toBeNull();
1374
+ expect(next.cellRuntime[CELL_B].consoleOutputs).toEqual([]);
1375
+ });
1376
+
1377
+ it("resetCellRuntimes starts the scratch cell over too", () => {
1378
+ const state = MockNotebook.notebookState({
1379
+ cellData: { [CELL_A]: { id: CELL_A, code: "1" } },
1380
+ cellRuntime: {
1381
+ [SCRATCH_CELL_ID]: {
1382
+ output: {
1383
+ channel: "output",
1384
+ mimetype: "text/plain",
1385
+ data: "scratch",
1386
+ timestamp: 0,
1387
+ },
1388
+ },
1389
+ },
3956
1390
  });
1391
+ const next = exportedForTesting.reducer(state, {
1392
+ type: "resetCellRuntimes",
1393
+ payload: undefined,
1394
+ });
1395
+
1396
+ expect(next.cellRuntime[SCRATCH_CELL_ID].output).toBeNull();
3957
1397
  });
3958
1398
 
3959
- it("preserves console-only hydration in WASM", () => {
3960
- const next = exportedForTesting.reducer(hydratedState(), {
3961
- type: "setCells",
3962
- payload: newCells,
1399
+ describe("resetCellRuntimes in WASM", () => {
1400
+ beforeEach(async () => {
1401
+ const { isWasm } = await import("@/core/wasm/utils");
1402
+ vi.mocked(isWasm).mockReturnValue(true);
3963
1403
  });
3964
1404
 
3965
- expect(next.cellRuntime[CELL_B].consoleOutputs).toHaveLength(1);
3966
- expect(next.cellRuntime[CELL_B].consoleOutputs[0]).toMatchObject({
3967
- data: "hydrated-B-stdout",
1405
+ afterEach(async () => {
1406
+ const { isWasm } = await import("@/core/wasm/utils");
1407
+ vi.mocked(isWasm).mockReturnValue(false);
3968
1408
  });
3969
- });
3970
1409
 
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,
1410
+ it("keeps hydrated outputs and console", () => {
1411
+ const next = exportedForTesting.reducer(hydratedState(), {
1412
+ type: "resetCellRuntimes",
1413
+ payload: undefined,
1414
+ });
1415
+
1416
+ expect(next.cellRuntime[CELL_A].output).toMatchObject({
1417
+ data: "hydrated-A",
1418
+ });
1419
+ expect(next.cellRuntime[CELL_B].consoleOutputs[0]).toMatchObject({
1420
+ data: "hydrated-B-stdout",
1421
+ });
3976
1422
  });
3977
1423
 
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([]);
1424
+ it("starts cells with no prior runtime over", () => {
1425
+ const state = MockNotebook.notebookState({
1426
+ cellData: { [CELL_A]: { id: CELL_A, code: "1" } },
1427
+ });
1428
+ const next = exportedForTesting.reducer(state, {
1429
+ type: "resetCellRuntimes",
1430
+ payload: undefined,
1431
+ });
1432
+
1433
+ expect(next.cellRuntime[CELL_A].output).toBeNull();
1434
+ expect(next.cellRuntime[CELL_A].consoleOutputs).toEqual([]);
1435
+ });
3982
1436
  });
3983
1437
  });