@marimo-team/islands 0.25.3-dev30 → 0.25.3-dev302

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 (1028) hide show
  1. package/dist/{ConnectedDataExplorerComponent-7EaP9VWb.js → ConnectedDataExplorerComponent-DJnSxHJB.js} +153 -177
  2. package/dist/{ErrorBoundary-2Tp_7IgE.js → ErrorBoundary-BDEkm7xA.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-CR0U5hup.js} +5 -5
  7. package/dist/any-language-editor-C5dsTpeB.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-Bc_fbI1Q.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-DzTYIHH_.js +1 -0
  17. package/dist/assets/worker-BH08I2Il.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-C4iLRzY0.js} +2142 -1869
  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-y9z5oeAY.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-Tu-V-e_v.js} +10 -10
  40. package/dist/{chunk-SVP7TREG-BNBrTAw1.js → chunk-SVP7TREG-CJFxs5R_.js} +3 -3
  41. package/dist/{chunk-TICWLB2K-DXNxvUJn.js → chunk-TICWLB2K-zkTP7ya7.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-DxJAZI0N.js +30 -0
  45. package/dist/classDiagram-v2-VYDZK3BY-TmHo2BpS.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-891-xGjh.js → code-visibility-NL_5uwhy.js} +43 -50
  51. package/dist/{coffeescript-C-eFwH5N.js → coffeescript-D6UgrGgo.js} +1 -1
  52. package/dist/{common-_IunO2z-.js → common-M3iB2q2_.js} +10624 -10120
  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-BG2qKzDg.js +633 -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-Eb40hmhD.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-_paaVwit.js} +14 -16
  69. package/dist/{data-grid-overlay-editor-TVf1Kaxm.js → data-grid-overlay-editor-BaQAbdKk.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-BtKJm1_4.js} +7 -15
  73. package/dist/{diagram-UQ7AKVKN-CvJbL_IM.js → diagram-UQ7AKVKN-DQbxhEMd.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-DITDIfGC.js} +7 -7
  76. package/dist/{diagram-Z3DM3KII-CyijwTR2.js → diagram-Z3DM3KII-Ct9_zo6F.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-6cOntwfv.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-CJiOER41.js} +14 -14
  114. package/dist/{erlang-AdO1-xtV.js → erlang-2KpKpfC7.js} +1 -1
  115. package/dist/{error-banner-CFPHFw4h.js → error-banner-DGOrmBk5.js} +91 -91
  116. package/dist/error-entries-BWmleiL0.js +2581 -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-CnhSwoRZ.js +370 -0
  123. package/dist/flowDiagram-HODETNUW-1lPBEUuW.js +15 -0
  124. package/dist/{formats-DRHUWwSK.js → formats-qhBmYb0l.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-ugYWecYd.js → glide-data-editor-Ca0ThLaf.js} +6619 -2875
  133. package/dist/{glide-utils-pisbYVjH.js → glide-utils-C4TKZeBA.js} +109 -78
  134. package/dist/{globals-BdBjL67a.js → globals-CI7fOwRe.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-B8hE6HIH.js} +26032 -26377
  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-C6K4tri5.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-DwcwgRNH.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-E489OMdc.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 +10380 -8501
  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-DMPMkVqY.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-DWHjasOp.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-I4AHjcHG.js → number-overlay-editor-uJBCKZNU.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-COjcPOZT.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-BHAVZ38h.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-DZDL_j1a.js} +5 -5
  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-94ThFyKX.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-CpF-Fxbb.js} +13 -13
  212. package/dist/{reveal-component-Bb5vzlMs.js → reveal-component-C8v8DMO2.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-CvANK2x7.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-WHuJ4mJL.js} +18 -18
  231. package/dist/stateDiagram-v2-MP3YSRHH-DbO7lwYq.js +30 -0
  232. package/dist/static-state-Dtocd9UO.js +68 -0
  233. package/dist/{stex-Btf3omL8.js → stex-CQ_aCGTF.js} +1 -1
  234. package/dist/style.css +1 -1
  235. package/dist/{stylus-Dt4VvLXL.js → stylus-D4gHF5lg.js} +1 -1
  236. package/dist/{swift-DHsn_CVI.js → swift-CnvIBnbt.js} +1 -1
  237. package/dist/{swimlanes-42K2YHIH-CqkGoGqn.js → swimlanes-42K2YHIH-CS2s29Vo.js} +37 -59
  238. package/dist/swimlanesDiagram-VR7AAH4N-CXaA1vQg.js +30 -0
  239. package/dist/{swiper-component-DKntyn8p.js → swiper-component-FBPvIqLf.js} +45 -78
  240. package/dist/{tcl-CV0OLBOl.js → tcl-DkOjjZYO.js} +1 -1
  241. package/dist/{textile-D320oKVM.js → textile-DgzBVSEW.js} +1 -1
  242. package/dist/{tiki-BF2aDPlB.js → tiki-MighF8A_.js} +1 -1
  243. package/dist/{time-BaMbJs4z.js → time-Cc-G3e31.js} +2 -2
  244. package/dist/{timeline-definition-24CTP7MA-DLRUNVAx.js → timeline-definition-24CTP7MA-B-SQMg5h.js} +5 -5
  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-DwqGfN3_.js} +167 -172
  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-etEFgvaG.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-BEQN7g62.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-D8-e5Z0R.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 +17 -21
  279. package/src/__mocks__/document.tsx +63 -0
  280. package/src/__mocks__/executions.ts +197 -0
  281. package/src/__mocks__/notebook.ts +3 -7
  282. package/src/__mocks__/partial.ts +26 -0
  283. package/src/__mocks__/requests.ts +1 -4
  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/name-cell-input.tsx +4 -3
  412. package/src/components/editor/actions/pair-with-agent-commands.ts +10 -7
  413. package/src/components/editor/actions/pair-with-agent-modal.tsx +29 -30
  414. package/src/components/editor/actions/pair-with-agent.tsx +16 -39
  415. package/src/components/editor/actions/useCellActionButton.tsx +47 -35
  416. package/src/components/editor/actions/useConfigActions.tsx +3 -4
  417. package/src/components/editor/actions/useNotebookActions.tsx +25 -29
  418. package/src/components/editor/actions/useRestartKernel.tsx +1 -1
  419. package/src/components/editor/actions/useSetCodeVisibility.ts +11 -12
  420. package/src/components/editor/actions/useSetOutputsExpanded.ts +14 -19
  421. package/src/components/editor/ai/ai-completion-editor.tsx +2 -1
  422. package/src/components/editor/ai/completion-handlers.tsx +0 -37
  423. package/src/components/editor/ai/prompt-input.tsx +189 -0
  424. package/src/components/editor/alerts/__tests__/connection-notice.test.tsx +1 -1
  425. package/src/components/editor/alerts/connection-notice.tsx +1 -1
  426. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  427. package/src/components/editor/alerts/stdin-blocking-alert.tsx +4 -2
  428. package/src/components/editor/app-container.tsx +1 -1
  429. package/src/components/editor/boundary/__tests__/ErrorBoundary.test.tsx +1 -1
  430. package/src/components/editor/cell/CellStatus.tsx +28 -23
  431. package/src/components/editor/cell/CreateCellButton.tsx +15 -21
  432. package/src/components/editor/cell/DeleteButton.tsx +3 -2
  433. package/src/components/editor/cell/RunButton.tsx +18 -8
  434. package/src/components/editor/cell/StagedAICell.tsx +52 -137
  435. package/src/components/editor/cell/StopButton.tsx +2 -2
  436. package/src/components/editor/cell/__tests__/useRunCells.test.tsx +169 -0
  437. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  438. package/src/components/editor/cell/cell-actions.tsx +12 -1
  439. package/src/components/editor/cell/cell-context-menu.tsx +4 -1
  440. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  441. package/src/components/editor/cell/code/cell-editor.tsx +65 -169
  442. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  443. package/src/components/editor/cell/collapse.tsx +16 -6
  444. package/src/components/editor/cell/useAddCell.ts +4 -8
  445. package/src/components/editor/cell/useDeleteCell.tsx +10 -36
  446. package/src/components/editor/cell/useRunCells.ts +49 -8
  447. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  448. package/src/components/editor/cell/useSplitCell.tsx +10 -6
  449. package/src/components/editor/chrome/components/__tests__/feedback-button.test.tsx +1 -1
  450. package/src/components/editor/chrome/components/feedback-button.tsx +2 -8
  451. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +31 -2
  452. package/src/components/editor/chrome/panels/__tests__/runtime-details.test.tsx +1 -1
  453. package/src/components/editor/chrome/panels/__tests__/sandbox-recovery.test.tsx +1 -1
  454. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  455. package/src/components/editor/chrome/panels/dependency-graph-panel.tsx +1 -1
  456. package/src/components/editor/chrome/panels/logs-panel.tsx +2 -1
  457. package/src/components/editor/chrome/panels/outline/__tests__/outline-keyboard.test.tsx +84 -0
  458. package/src/components/editor/chrome/panels/outline/floating-outline.tsx +39 -14
  459. package/src/components/editor/chrome/panels/outline/useActiveOutline.tsx +3 -2
  460. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  461. package/src/components/editor/chrome/panels/panel-header.tsx +13 -0
  462. package/src/components/editor/chrome/panels/secrets-panel.tsx +1 -0
  463. package/src/components/editor/chrome/panels/session-panel.tsx +1 -1
  464. package/src/components/editor/chrome/panels/snippets-panel.tsx +10 -12
  465. package/src/components/editor/chrome/panels/variable-panel.tsx +1 -5
  466. package/src/components/editor/chrome/types.ts +1 -1
  467. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +7 -9
  468. package/src/components/editor/chrome/wrapper/__tests__/document-sync-status.test.tsx +92 -0
  469. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +265 -37
  470. package/src/components/editor/chrome/wrapper/__tests__/useAskAiAboutCell.test.tsx +58 -0
  471. package/src/components/editor/chrome/wrapper/app-chrome.tsx +91 -74
  472. package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +4 -4
  473. package/src/components/editor/chrome/wrapper/footer-items/backend-status.tsx +1 -1
  474. package/src/components/editor/chrome/wrapper/footer-items/document-sync-status.tsx +54 -0
  475. package/src/components/editor/chrome/wrapper/footer-items/lsp-status.tsx +1 -1
  476. package/src/components/editor/chrome/wrapper/footer-items/machine-stats.tsx +1 -1
  477. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  478. package/src/components/editor/chrome/wrapper/footer.tsx +2 -2
  479. package/src/components/editor/chrome/wrapper/minimap-state.ts +1 -1
  480. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +143 -72
  481. package/src/components/editor/chrome/wrapper/pending-ai-changes-badge.tsx +25 -0
  482. package/src/components/editor/chrome/wrapper/sidebar.tsx +8 -4
  483. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  484. package/src/components/editor/chrome/wrapper/useAskAiAboutCell.ts +36 -0
  485. package/src/components/editor/columns/__tests__/cell-column.test.tsx +1 -1
  486. package/src/components/editor/columns/cell-column.tsx +1 -1
  487. package/src/components/editor/columns/sortable-column.tsx +2 -2
  488. package/src/components/editor/connections/components.tsx +5 -10
  489. package/src/components/editor/controls/Controls.tsx +4 -4
  490. package/src/components/editor/controls/__tests__/keyboard-shortcuts.test.tsx +61 -0
  491. package/src/components/editor/controls/__tests__/notebook-menu-dropdown.test.tsx +10 -4
  492. package/src/components/editor/controls/keyboard-shortcuts.tsx +14 -5
  493. package/src/components/editor/documentation.css +34 -11
  494. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +26 -22
  495. package/src/components/editor/errors/auto-fix.tsx +4 -8
  496. package/src/components/editor/errors/send-error-report-button.tsx +3 -6
  497. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  498. package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +47 -0
  499. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  500. package/src/components/editor/file-tree/file-explorer.tsx +48 -15
  501. package/src/components/editor/file-tree/file-name-input.tsx +1 -0
  502. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  503. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  504. package/src/components/editor/header/__tests__/filename-form.test.tsx +1 -4
  505. package/src/components/editor/header/__tests__/status.test.tsx +5 -3
  506. package/src/components/editor/header/app-header.tsx +1 -1
  507. package/src/components/editor/header/filename-form.tsx +1 -1
  508. package/src/components/editor/header/status.tsx +44 -22
  509. package/src/components/editor/links/__tests__/cell-link.test.tsx +148 -0
  510. package/src/components/editor/links/cell-link-list.tsx +1 -1
  511. package/src/components/editor/links/cell-link.tsx +21 -11
  512. package/src/components/editor/navigation/__tests__/clipboard.test.ts +60 -38
  513. package/src/components/editor/navigation/__tests__/navigation.test.ts +109 -85
  514. package/src/components/editor/navigation/__tests__/selection.test.ts +43 -21
  515. package/src/components/editor/navigation/clipboard.ts +17 -14
  516. package/src/components/editor/navigation/focus-utils.ts +23 -4
  517. package/src/components/editor/navigation/multi-cell-action-toolbar.tsx +17 -33
  518. package/src/components/editor/navigation/navigation.ts +74 -72
  519. package/src/components/editor/navigation/selection.ts +30 -29
  520. package/src/components/editor/notebook-banner.tsx +1 -0
  521. package/src/components/editor/notebook-cell.tsx +126 -152
  522. package/src/components/editor/output/MarimoErrorOutput.tsx +41 -4
  523. package/src/components/editor/output/MarimoTracebackOutput.tsx +19 -17
  524. package/src/components/editor/output/Outputs.css +8 -0
  525. package/src/components/editor/output/VideoOutput.tsx +6 -0
  526. package/src/components/editor/output/__tests__/VideoOutput.test.tsx +131 -0
  527. package/src/components/editor/output/__tests__/traceback.test.tsx +27 -1
  528. package/src/components/editor/output/ansi-reduce.ts +33 -20
  529. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  530. package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +125 -69
  531. package/src/components/editor/package-alert.tsx +14 -7
  532. package/src/components/editor/renderers/__tests__/add-cell-buttons.test.tsx +18 -0
  533. package/src/components/editor/renderers/cell-array.tsx +32 -100
  534. package/src/components/editor/renderers/grid-layout/__tests__/grid-layout.test.tsx +75 -0
  535. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +28 -22
  536. package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +0 -4
  537. package/src/components/editor/renderers/slides-layout/slides-layout.tsx +1 -1
  538. package/src/components/editor/renderers/vertical-layout/__tests__/useFocusFirstEditor.test.ts +4 -4
  539. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  540. package/src/components/editor/renderers/vertical-layout/useFocusFirstEditor.ts +1 -1
  541. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +19 -37
  542. package/src/components/editor/stdin-blocking-alert.tsx +4 -2
  543. package/src/components/home/components.tsx +17 -31
  544. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  545. package/src/components/icons/large-spinner.tsx +3 -14
  546. package/src/components/icons/spinner.tsx +1 -1
  547. package/src/components/markdown/__tests__/markdown-renderer.test.tsx +32 -2
  548. package/src/components/markdown/markdown-renderer.tsx +43 -28
  549. package/src/components/mcp/mcp-status-indicator.tsx +18 -14
  550. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  551. package/src/components/pages/edit-page.tsx +19 -17
  552. package/src/components/pages/home-page.tsx +164 -91
  553. package/src/components/scratchpad/scratchpad.test.tsx +89 -0
  554. package/src/components/scratchpad/scratchpad.tsx +47 -33
  555. package/src/components/scratchpad/state.ts +7 -0
  556. package/src/components/slides/__tests__/minimap-actions.test.tsx +22 -29
  557. package/src/components/slides/__tests__/minimap.test.ts +38 -19
  558. package/src/components/slides/__tests__/slide-scroll-hint.test.tsx +37 -0
  559. package/src/components/slides/minimap.tsx +22 -14
  560. package/src/components/slides/reveal-component.tsx +73 -36
  561. package/src/components/slides/reveal-slides.css +5 -4
  562. package/src/components/slides/slide-cell-view.tsx +37 -34
  563. package/src/components/slides/slide-form.tsx +1 -0
  564. package/src/components/slides/slide-scroll-hint.tsx +3 -1
  565. package/src/components/slides/slide.tsx +3 -4
  566. package/src/components/sort/SortableCellsProvider.tsx +8 -25
  567. package/src/components/static-html/__tests__/static-banner.test.tsx +1 -1
  568. package/src/components/tracing/tracing-keyboard.test.tsx +85 -0
  569. package/src/components/tracing/tracing.tsx +46 -42
  570. package/src/components/ui/__tests__/draggable-popover.test.tsx +24 -0
  571. package/src/components/ui/__tests__/fullscreen.test.tsx +44 -2
  572. package/src/components/ui/__tests__/range-slider.test.tsx +165 -0
  573. package/src/components/ui/alert-dialog.tsx +1 -1
  574. package/src/components/ui/draggable-popover.tsx +24 -2
  575. package/src/components/ui/fullscreen.tsx +25 -1
  576. package/src/components/ui/range-slider.tsx +22 -9
  577. package/src/components/ui/toast.tsx +1 -1
  578. package/src/core/MarimoApp.tsx +3 -1
  579. package/src/core/ai/__tests__/staged-cells.test.ts +89 -608
  580. package/src/core/ai/context/context.ts +2 -0
  581. package/src/core/ai/context/providers/__tests__/cell-output.test.ts +8 -12
  582. package/src/core/ai/context/providers/__tests__/cell.test.ts +49 -0
  583. package/src/core/ai/context/providers/cell-output.ts +2 -2
  584. package/src/core/ai/context/providers/cell.ts +54 -0
  585. package/src/core/ai/context/providers/datasource.ts +6 -2
  586. package/src/core/ai/staged-cells.ts +56 -323
  587. package/src/core/ai/state.ts +3 -0
  588. package/src/core/ai/tools/__tests__/edit-notebook-tool.test.ts +47 -91
  589. package/src/core/ai/tools/__tests__/registry.test.ts +17 -0
  590. package/src/core/ai/tools/base.ts +18 -15
  591. package/src/core/ai/tools/edit-notebook-tool.ts +86 -80
  592. package/src/core/ai/tools/registry.ts +13 -10
  593. package/src/core/ai/tools/sample-tool.ts +6 -7
  594. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -529
  595. package/src/core/cells/__tests__/actions.test.ts +623 -35
  596. package/src/core/cells/__tests__/add-missing-import.test.ts +7 -8
  597. package/src/core/cells/__tests__/arrange.test.ts +432 -0
  598. package/src/core/cells/__tests__/cell.test.ts +148 -150
  599. package/src/core/cells/__tests__/cells.test.ts +394 -2926
  600. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
  601. package/src/core/cells/__tests__/document.test.ts +158 -0
  602. package/src/core/cells/__tests__/editor.test.ts +57 -0
  603. package/src/core/cells/__tests__/flush-document.test.ts +40 -0
  604. package/src/core/cells/__tests__/harness.ts +109 -0
  605. package/src/core/cells/__tests__/layout.test.ts +176 -0
  606. package/src/core/cells/__tests__/logs.test.ts +0 -13
  607. package/src/core/cells/__tests__/notebook-outline.test.ts +23 -14
  608. package/src/core/cells/__tests__/outputs.test.tsx +71 -53
  609. package/src/core/cells/__tests__/pending-delete-service.test.tsx +11 -20
  610. package/src/core/cells/__tests__/session.test.ts +546 -211
  611. package/src/core/cells/__tests__/utils.test.ts +135 -337
  612. package/src/core/cells/actions.ts +677 -6
  613. package/src/core/cells/add-missing-import.ts +47 -43
  614. package/src/core/cells/arrange.ts +529 -0
  615. package/src/core/cells/cell.ts +78 -146
  616. package/src/core/cells/cells.ts +107 -1459
  617. package/src/core/cells/columns.ts +38 -0
  618. package/src/core/cells/document.ts +167 -0
  619. package/src/core/cells/edit.ts +30 -0
  620. package/src/core/cells/editor.ts +82 -0
  621. package/src/core/cells/focus.ts +27 -6
  622. package/src/core/cells/layout.ts +221 -0
  623. package/src/core/cells/outputs.ts +12 -28
  624. package/src/core/cells/pending-delete-service.ts +8 -14
  625. package/src/core/cells/scrollCellIntoView.ts +6 -0
  626. package/src/core/cells/session.ts +166 -68
  627. package/src/core/cells/types.ts +21 -51
  628. package/src/core/cells/utils.ts +85 -87
  629. package/src/core/codemirror/__tests__/format.test.ts +4 -35
  630. package/src/core/codemirror/__tests__/setup.test.ts +67 -33
  631. package/src/core/codemirror/ai/__tests__/resources.test.ts +117 -0
  632. package/src/core/codemirror/ai/resources.ts +1 -1
  633. package/src/core/codemirror/cells/__tests__/extensions.test.ts +2 -6
  634. package/src/core/codemirror/cells/extensions.ts +18 -30
  635. package/src/core/codemirror/cells/state.ts +2 -6
  636. package/src/core/codemirror/cm.ts +5 -1
  637. package/src/core/codemirror/config/types.ts +1 -1
  638. package/src/core/codemirror/copilot/__tests__/completion-integration.test.ts +4 -0
  639. package/src/core/codemirror/copilot/__tests__/getCodes.test.ts +13 -10
  640. package/src/core/codemirror/copilot/getCodes.ts +2 -2
  641. package/src/core/codemirror/format.ts +6 -23
  642. package/src/core/codemirror/keymaps/vim.ts +4 -8
  643. package/src/core/codemirror/language/__tests__/extension.test.ts +7 -3
  644. package/src/core/codemirror/language/__tests__/sql.test.ts +22 -20
  645. package/src/core/codemirror/language/__tests__/utils.test.ts +194 -17
  646. package/src/core/codemirror/language/cell-code.ts +114 -0
  647. package/src/core/codemirror/language/extension.ts +52 -35
  648. package/src/core/codemirror/language/languages/markdown.ts +8 -6
  649. package/src/core/codemirror/language/languages/python.ts +19 -13
  650. package/src/core/codemirror/language/languages/sql/sql.ts +16 -24
  651. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  652. package/src/core/codemirror/language/types.ts +7 -7
  653. package/src/core/codemirror/language/utils.ts +9 -39
  654. package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +1 -2
  655. package/src/core/codemirror/lsp/notebook-lsp.ts +6 -5
  656. package/src/core/codemirror/lsp/utils.ts +1 -1
  657. package/src/core/codemirror/misc/__tests__/paste.test.ts +3 -1
  658. package/src/core/codemirror/misc/paste.ts +1 -1
  659. package/src/core/codemirror/placeholder/__tests__/extensions.test.ts +75 -0
  660. package/src/core/codemirror/placeholder/extensions.ts +80 -19
  661. package/src/core/codemirror/readonly/__tests__/extension.test.ts +1 -1
  662. package/src/core/codemirror/readonly/extension.ts +2 -2
  663. package/src/core/codemirror/utils.ts +1 -0
  664. package/src/core/config/__tests__/app-config.test.ts +64 -0
  665. package/src/core/config/__tests__/config-schema.test.ts +177 -17
  666. package/src/core/config/config-schema.ts +116 -18
  667. package/src/core/config/config.ts +65 -20
  668. package/src/core/config/feature-flag.tsx +0 -2
  669. package/src/core/datasets/__tests__/data-source-discovery.test.ts +5 -5
  670. package/src/core/datasets/data-source-discovery.ts +4 -6
  671. package/src/core/documentation/doc-lookup.ts +1 -1
  672. package/src/core/dom/outline.ts +4 -1
  673. package/src/core/edit-app.tsx +22 -28
  674. package/src/core/errors/__tests__/error-entries.test.ts +40 -1
  675. package/src/core/errors/error-entries.ts +6 -4
  676. package/src/core/executions/__tests__/atoms.test.ts +98 -0
  677. package/src/core/executions/__tests__/freshness.test.ts +482 -0
  678. package/src/core/executions/__tests__/hooks.test.tsx +151 -0
  679. package/src/core/executions/__tests__/output.test.ts +266 -0
  680. package/src/core/executions/__tests__/select.test.ts +268 -0
  681. package/src/core/executions/atoms.ts +263 -0
  682. package/src/core/executions/freshness.ts +171 -0
  683. package/src/core/executions/hooks.ts +86 -0
  684. package/src/core/executions/index.ts +48 -0
  685. package/src/core/executions/output.ts +242 -0
  686. package/src/core/executions/select.ts +175 -0
  687. package/src/core/export/__tests__/hooks.test.ts +0 -5
  688. package/src/core/export/hooks.ts +1 -1
  689. package/src/core/export/layout.ts +2 -2
  690. package/src/core/hotkeys/actions.ts +2 -4
  691. package/src/core/hotkeys/hotkeys.ts +2 -1
  692. package/src/core/islands/__tests__/bootstrap.test.ts +69 -17
  693. package/src/core/islands/__tests__/bridge.test.ts +85 -27
  694. package/src/core/islands/bootstrap.ts +49 -54
  695. package/src/core/islands/bridge.ts +84 -25
  696. package/src/core/islands/components/__tests__/IslandControls.test.tsx +7 -5
  697. package/src/core/islands/components/__tests__/web-components.test.tsx +63 -0
  698. package/src/core/islands/components/output-wrapper.tsx +5 -5
  699. package/src/core/islands/components/web-components.tsx +12 -1
  700. package/src/core/islands/worker/__tests__/controller.test.ts +62 -4
  701. package/src/core/islands/worker/controller.ts +4 -10
  702. package/src/core/islands/worker/worker.tsx +33 -33
  703. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  704. package/src/core/kernel/__tests__/handlers.test.ts +7 -157
  705. package/src/core/kernel/handlers.ts +13 -79
  706. package/src/core/kernel/state.ts +0 -15
  707. package/src/core/lifecycle/render-policy.ts +12 -26
  708. package/src/core/meta/__tests__/code-visibility.test.tsx +78 -70
  709. package/src/core/meta/code-visibility.ts +18 -9
  710. package/src/core/network/__tests__/connection.test.ts +1 -1
  711. package/src/core/network/__tests__/requests-lazy.test.ts +6 -8
  712. package/src/core/network/connection-notice.ts +1 -1
  713. package/src/core/network/connection.ts +1 -1
  714. package/src/core/network/requests-lazy.ts +5 -8
  715. package/src/core/network/requests-network.ts +5 -31
  716. package/src/core/network/requests-static.ts +1 -4
  717. package/src/core/network/requests-toasting.tsx +1 -4
  718. package/src/core/network/types.ts +6 -14
  719. package/src/core/network/useConnectionNotice.ts +8 -2
  720. package/src/core/notebook/__tests__/operations.test.ts +51 -0
  721. package/src/core/notebook/attachments.ts +38 -0
  722. package/src/core/notebook/document/__tests__/codemirror.test.ts +765 -0
  723. package/src/core/notebook/document/__tests__/draft.test.ts +363 -0
  724. package/src/core/notebook/document/__tests__/fixtures.ts +92 -0
  725. package/src/core/notebook/document/__tests__/hooks.test.tsx +391 -0
  726. package/src/core/notebook/document/__tests__/lifecycle-scenarios.test.ts +61 -0
  727. package/src/core/notebook/document/__tests__/model.test.ts +247 -0
  728. package/src/core/notebook/document/__tests__/protocol-fixtures.ts +38 -0
  729. package/src/core/notebook/document/__tests__/recovery-scenarios.test.ts +230 -0
  730. package/src/core/notebook/document/__tests__/scenario-fixture.ts +43 -0
  731. package/src/core/notebook/document/__tests__/scenarios.test.ts +205 -0
  732. package/src/core/notebook/document/__tests__/state.test.ts +459 -0
  733. package/src/core/notebook/document/__tests__/submission.test.ts +203 -0
  734. package/src/core/notebook/document/__tests__/test-host.test.ts +92 -0
  735. package/src/core/notebook/document/__tests__/test-host.ts +247 -0
  736. package/src/core/notebook/document/__tests__/test-notebook.ts +149 -0
  737. package/src/core/notebook/document/__tests__/user.ts +61 -0
  738. package/src/core/notebook/document/codemirror.ts +216 -0
  739. package/src/core/notebook/document/draft.ts +502 -0
  740. package/src/core/notebook/document/hooks.tsx +118 -0
  741. package/src/core/notebook/document/index.ts +24 -0
  742. package/src/core/notebook/document/model.ts +102 -0
  743. package/src/core/notebook/document/select.ts +41 -0
  744. package/src/core/notebook/document/state.ts +378 -0
  745. package/src/core/notebook/document/sync.ts +369 -0
  746. package/src/core/notebook/index.ts +19 -0
  747. package/src/core/notebook/operations.ts +30 -0
  748. package/src/core/notebook/replica/AGENTS.md +11 -0
  749. package/src/core/{host → notebook/replica}/__tests__/attachments.test.ts +9 -10
  750. package/src/core/{host → notebook/replica}/__tests__/document.test.ts +18 -16
  751. package/src/core/notebook/replica/__tests__/executions.test.ts +200 -0
  752. package/src/core/notebook/replica/__tests__/fixtures.ts +79 -0
  753. package/src/core/notebook/replica/__tests__/replica.test.ts +559 -0
  754. package/src/core/notebook/replica/__tests__/results.test.ts +193 -0
  755. package/src/core/notebook/replica/__tests__/runtime.test.ts +78 -0
  756. package/src/core/notebook/replica/backend.ts +19 -0
  757. package/src/core/notebook/replica/connection.ts +42 -0
  758. package/src/core/notebook/replica/index.ts +20 -0
  759. package/src/core/notebook/replica/replica.ts +336 -0
  760. package/src/core/{host → notebook/replica}/resources/attachments.ts +6 -3
  761. package/src/core/{host → notebook/replica}/resources/document.ts +3 -3
  762. package/src/core/notebook/replica/resources/executions.ts +157 -0
  763. package/src/core/notebook/replica/resources/results.ts +48 -0
  764. package/src/core/notebook/replica/resources/runtime.ts +14 -0
  765. package/src/core/packages/__tests__/useSandboxController.test.tsx +1 -1
  766. package/src/core/packages/useSandboxController.ts +1 -1
  767. package/src/core/run-app.tsx +49 -51
  768. package/src/core/runtime/__tests__/adapter.test.ts +4 -4
  769. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  770. package/src/core/runtime/__tests__/runtime.test.ts +32 -74
  771. package/src/core/runtime/adapter.ts +5 -5
  772. package/src/core/runtime/config.ts +2 -2
  773. package/src/core/runtime/runtime.ts +33 -46
  774. package/src/core/runtime/types.ts +0 -2
  775. package/src/core/saving/__tests__/document.test.tsx +123 -0
  776. package/src/core/saving/__tests__/state.test.ts +111 -0
  777. package/src/core/saving/__tests__/useAutoSave.test.tsx +67 -0
  778. package/src/core/saving/document.ts +33 -0
  779. package/src/core/saving/filename.ts +1 -1
  780. package/src/core/saving/save-component.tsx +39 -60
  781. package/src/core/saving/state.ts +38 -39
  782. package/src/core/saving/useAutoSave.ts +17 -53
  783. package/src/core/session/__tests__/backend.test.ts +236 -0
  784. package/src/core/session/__tests__/useNotebookReplica.hook.test.tsx +186 -0
  785. package/src/core/{websocket → session}/__tests__/useNotebookSession.hook.test.tsx +245 -262
  786. package/src/core/session/active-notebook.ts +28 -0
  787. package/src/core/session/backend.ts +123 -0
  788. package/src/core/session/embedded.ts +7 -0
  789. package/src/core/{websocket → session}/types.ts +2 -2
  790. package/src/core/session/useNotebookReplica.ts +74 -0
  791. package/src/core/{websocket → session}/useNotebookSession.tsx +80 -67
  792. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  793. package/src/core/state/jotai.ts +1 -1
  794. package/src/core/static/__tests__/files.test.ts +38 -10
  795. package/src/core/static/files.ts +2 -0
  796. package/src/core/{websocket → transports}/__tests__/connection-state.test.ts +1 -13
  797. package/src/core/transports/__tests__/server.test.ts +408 -0
  798. package/src/core/transports/__tests__/static.test.ts +340 -0
  799. package/src/core/transports/__tests__/wasm.test.ts +166 -0
  800. package/src/core/{websocket → transports}/connection-state.ts +9 -16
  801. package/src/core/transports/server.ts +152 -0
  802. package/src/core/transports/static.ts +173 -0
  803. package/src/core/transports/wasm.ts +97 -0
  804. package/src/core/vscode/__tests__/vscode-bindings.test.ts +125 -0
  805. package/src/core/vscode/vscode-bindings.ts +14 -2
  806. package/src/core/wasm/__tests__/bridge.test.ts +210 -64
  807. package/src/core/wasm/__tests__/worker.test.ts +188 -0
  808. package/src/core/wasm/bridge.ts +120 -73
  809. package/src/core/wasm/rpc.ts +15 -3
  810. package/src/core/wasm/store.ts +3 -3
  811. package/src/core/wasm/worker/bootstrap.ts +55 -51
  812. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  813. package/src/core/wasm/worker/save-worker.ts +9 -10
  814. package/src/core/wasm/worker/types.ts +30 -18
  815. package/src/core/wasm/worker/worker.ts +118 -72
  816. package/src/css/app/Cell.css +7 -36
  817. package/src/css/app/codemirror.css +1 -1
  818. package/src/css/common.css +1 -1
  819. package/src/css/globals.css +72 -24
  820. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  821. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  822. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  823. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  824. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  825. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  826. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  827. package/src/hooks/useAsyncData.ts +3 -3
  828. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  829. package/src/hooks/useDebounce.ts +6 -10
  830. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  831. package/src/hooks/useDelayElapsed.ts +14 -10
  832. package/src/hooks/useElapsedTime.ts +3 -3
  833. package/src/hooks/useEventListener.ts +1 -2
  834. package/src/hooks/useInputHistory.ts +4 -7
  835. package/src/hooks/useInternalStateWithSync.ts +6 -6
  836. package/src/hooks/useResizeObserver.ts +4 -7
  837. package/src/hooks/useScript.ts +67 -70
  838. package/src/hooks/useStateForKey.ts +93 -0
  839. package/src/mount.tsx +20 -15
  840. package/src/plugins/__tests__/plugin-object-schemas.test.ts +104 -0
  841. package/src/plugins/core/RenderHTML.tsx +3 -5
  842. package/src/plugins/core/__test__/PluginSlot.test.tsx +202 -0
  843. package/src/plugins/core/__test__/RenderHTML-copy.test.tsx +60 -0
  844. package/src/plugins/core/__test__/registerReactComponent.test.ts +45 -0
  845. package/src/plugins/core/__test__/sanitize.test.ts +8 -0
  846. package/src/plugins/core/registerReactComponent.tsx +29 -29
  847. package/src/plugins/core/sanitize-html.ts +6 -0
  848. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  849. package/src/plugins/impl/DataEditorPlugin.tsx +34 -13
  850. package/src/plugins/impl/DataTablePlugin.tsx +69 -37
  851. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  852. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  853. package/src/plugins/impl/FormPlugin.tsx +1 -1
  854. package/src/plugins/impl/RangeSliderPlugin.tsx +16 -2
  855. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  856. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  857. package/src/plugins/impl/__tests__/DataEditorPlugin.test.ts +93 -4
  858. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +387 -1
  859. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  860. package/src/plugins/impl/__tests__/RangeSliderPlugin.test.tsx +109 -0
  861. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  862. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +153 -0
  863. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  864. package/src/plugins/impl/anywidget/model.ts +3 -1
  865. package/src/plugins/impl/anywidget/registry.ts +18 -14
  866. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  867. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  868. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  869. package/src/plugins/impl/chat/chat-ui.tsx +17 -15
  870. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  871. package/src/plugins/impl/common/error-banner.tsx +5 -3
  872. package/src/plugins/impl/data-editor/__tests__/column-types.test.ts +42 -0
  873. package/src/plugins/impl/data-editor/__tests__/editor-state.test.ts +52 -1
  874. package/src/plugins/impl/data-editor/__tests__/glide-data-editor.test.tsx +120 -4
  875. package/src/plugins/impl/data-editor/__tests__/glide-utils.test.ts +116 -0
  876. package/src/plugins/impl/data-editor/column-types.ts +55 -0
  877. package/src/plugins/impl/data-editor/editor-state.ts +18 -2
  878. package/src/plugins/impl/data-editor/glide-data-editor.tsx +74 -12
  879. package/src/plugins/impl/data-editor/glide-utils.ts +39 -38
  880. package/src/plugins/impl/data-editor/types.ts +5 -0
  881. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  882. package/src/plugins/impl/data-explorer/components/column-summary.tsx +4 -2
  883. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  884. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -2
  885. package/src/plugins/impl/data-frames/__tests__/sidebar.test.tsx +160 -0
  886. package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +66 -66
  887. package/src/plugins/impl/data-frames/panel.tsx +44 -16
  888. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  889. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  890. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  891. package/src/plugins/impl/panel/utils.ts +17 -0
  892. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +2 -5
  893. package/src/plugins/impl/plotly/selection.ts +25 -19
  894. package/src/plugins/impl/vega/VegaPlugin.tsx +2 -3
  895. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  896. package/src/plugins/layout/DownloadPlugin.tsx +7 -11
  897. package/src/plugins/layout/LazyPlugin.tsx +7 -4
  898. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  899. package/src/plugins/layout/__test__/DownloadPlugin.test.tsx +205 -0
  900. package/src/plugins/layout/__tests__/LazyPlugin.test.tsx +98 -0
  901. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  902. package/src/stories/button.stories.tsx +1 -0
  903. package/src/stories/cell-status.stories.tsx +29 -24
  904. package/src/stories/cell.stories.tsx +46 -46
  905. package/src/stories/data-table.stories.tsx +31 -0
  906. package/src/stories/editor.stories.tsx +28 -17
  907. package/src/stories/form-wrapper.stories.tsx +2 -2
  908. package/src/stories/layout/vertical/one-column.stories.tsx +28 -42
  909. package/src/stories/runtime-story.tsx +23 -17
  910. package/src/stories/select.stories.tsx +2 -2
  911. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  912. package/src/stories/theme.stories.tsx +4 -1
  913. package/src/theme/__tests__/islands-theme.test.tsx +276 -0
  914. package/src/theme/namespace.tsx +4 -3
  915. package/src/theme/useTheme.ts +71 -41
  916. package/src/utils/__tests__/arrays.test.ts +35 -0
  917. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  918. package/src/utils/__tests__/{createReducer.test.ts → createReducer.test.tsx} +127 -1
  919. package/src/utils/__tests__/download.test.tsx +183 -23
  920. package/src/utils/__tests__/numbers.test.ts +105 -1
  921. package/src/utils/__tests__/objects.test.ts +21 -0
  922. package/src/utils/arrays.ts +11 -2
  923. package/src/utils/blob.ts +1 -1
  924. package/src/utils/createReducer.ts +115 -79
  925. package/src/utils/download.ts +67 -6
  926. package/src/utils/maps.ts +0 -3
  927. package/src/utils/numbers.ts +79 -18
  928. package/src/utils/objects.ts +4 -9
  929. package/src/utils/time.ts +10 -0
  930. package/src/utils/tracer.ts +9 -5
  931. package/src/utils/zod-utils.ts +1 -3
  932. package/dist/_baseMerge-BiGRwuqv.js +0 -165
  933. package/dist/any-language-editor-D4q-IFcM.js +0 -76
  934. package/dist/architecture-7GRP2DOG-BWLylgiA.js +0 -3
  935. package/dist/assets/__vite-browser-external-BXYkiFsk.js +0 -1
  936. package/dist/assets/worker-HPY9fbhz.js +0 -114
  937. package/dist/classDiagram-ZZMXUADV-DXdCCDOg.js +0 -30
  938. package/dist/classDiagram-v2-VYDZK3BY-DyZqrtLP.js +0 -30
  939. package/dist/cynefin-OW5HDTMX-obq-i80l.js +0 -3
  940. package/dist/dropdown-menu-BuE2b8Yb.js +0 -4678
  941. package/dist/eventmodeling-NTZA5JFV-D7nP7Uyu.js +0 -3
  942. package/dist/feedback-button-DlBBr16w.js +0 -379
  943. package/dist/flowDiagram-HODETNUW-DQ4JuadE.js +0 -16
  944. package/dist/gitGraph-4MIJSDKK-Dy9uQXGh.js +0 -3
  945. package/dist/hasIn-o2Us0uVx.js +0 -132
  946. package/dist/html-to-image-BsG_zyNW.js +0 -3251
  947. package/dist/info-A6RAGUB7-DKoayiHY.js +0 -3
  948. package/dist/isArrayLikeObject-BZl-15ir.js +0 -450
  949. package/dist/isEmpty-BSZ1LaUo.js +0 -35
  950. package/dist/isSymbol-kQcfYbNX.js +0 -8
  951. package/dist/label-DoysmLMW.js +0 -281
  952. package/dist/mermaid-HWGCJPDP-Du3-HPQA.js +0 -2
  953. package/dist/packet-AYTQ26CC-Yw7ajchJ.js +0 -3
  954. package/dist/pick-CJeoxYOS.js +0 -51
  955. package/dist/pie-WAS4IAKB-BKdKOAkL.js +0 -3
  956. package/dist/purify.es-DMv5zIkJ.js +0 -866
  957. package/dist/radar-RG4KPBEZ-ScR_D_Ts.js +0 -3
  958. package/dist/railroad-74A4TZTK-DQvPSdo5.js +0 -3
  959. package/dist/railroad-abnf-HS5TGJTU-CsJQG2O8.js +0 -3
  960. package/dist/railroad-ebnf-LZEXJU2U-BgEobkSn.js +0 -3
  961. package/dist/railroad-peg-WCYAUIDC-DjLAjofI.js +0 -3
  962. package/dist/select-DemoKT4G.js +0 -1344
  963. package/dist/stateDiagram-v2-MP3YSRHH-1OQRe6iA.js +0 -30
  964. package/dist/swimlanesDiagram-VR7AAH4N-CszjkrKo.js +0 -30
  965. package/dist/toDate-gyi35XS2.js +0 -1185
  966. package/dist/toast-PkdZraM8.js +0 -1830
  967. package/dist/treeView-Q6P3EWNA-DuRV97YF.js +0 -3
  968. package/dist/treemap-WGGIJYW6-AsLq-uwC.js +0 -3
  969. package/dist/use-restore-focus-CKjfFjLM.js +0 -6369
  970. package/dist/useLifecycle-Dfq8UD-b.js +0 -169
  971. package/dist/useTheme-Bu5bBgrh.js +0 -904
  972. package/dist/wardley-WFR3VGLG-Na-tFhQJ.js +0 -3
  973. package/src/components/chat/reasoning-accordion.tsx +0 -61
  974. package/src/components/chat/tool-call/tool-error-card.tsx +0 -81
  975. package/src/components/chat/tool-call/tool-history-row.tsx +0 -174
  976. package/src/components/editor/ai/__tests__/staged-cell-submission.test.ts +0 -106
  977. package/src/components/editor/ai/add-cell-with-ai.tsx +0 -506
  978. package/src/components/editor/ai/staged-cell-submission.ts +0 -46
  979. package/src/components/editor/cell/__tests__/StagedAICell.test.tsx +0 -64
  980. package/src/components/editor/cell/code/__tests__/rtc-editor-config.test.ts +0 -50
  981. package/src/components/editor/chrome/wrapper/footer-items/rtc-status.tsx +0 -65
  982. package/src/components/editor/navigation/__tests__/state.test.ts +0 -81
  983. package/src/components/editor/navigation/state.ts +0 -65
  984. package/src/components/icons/loading-ellipsis.tsx +0 -46
  985. package/src/core/ai/tools/__tests__/run-cells-tool.test.ts +0 -656
  986. package/src/core/ai/tools/run-cells-tool.ts +0 -303
  987. package/src/core/attachments/state.ts +0 -18
  988. package/src/core/cells/__tests__/apply-transaction.test.ts +0 -991
  989. package/src/core/cells/__tests__/document-changes.test.ts +0 -1140
  990. package/src/core/cells/__tests__/document-roundtrip.test.ts +0 -392
  991. package/src/core/cells/document-changes.ts +0 -944
  992. package/src/core/codemirror/rtc/extension.ts +0 -453
  993. package/src/core/codemirror/rtc/loro/__tests__/sync.test.ts +0 -52
  994. package/src/core/codemirror/rtc/loro/awareness.ts +0 -531
  995. package/src/core/codemirror/rtc/loro/colors.ts +0 -38
  996. package/src/core/codemirror/rtc/loro/sync.ts +0 -168
  997. package/src/core/host/AGENTS.md +0 -11
  998. package/src/core/host/__tests__/events.test.ts +0 -46
  999. package/src/core/host/__tests__/fixtures.ts +0 -39
  1000. package/src/core/host/__tests__/notebook-client.test.ts +0 -433
  1001. package/src/core/host/__tests__/protocol.test.ts +0 -229
  1002. package/src/core/host/__tests__/stream.test.ts +0 -209
  1003. package/src/core/host/connection.ts +0 -81
  1004. package/src/core/host/events.ts +0 -61
  1005. package/src/core/host/notebook-client.ts +0 -290
  1006. package/src/core/host/protocol.ts +0 -1948
  1007. package/src/core/host/resources/resource.ts +0 -47
  1008. package/src/core/host/stream.ts +0 -211
  1009. package/src/core/rtc/state.ts +0 -34
  1010. package/src/core/state/__tests__/jotai.test.ts +0 -25
  1011. package/src/core/wasm/worker/message-buffer.ts +0 -33
  1012. package/src/core/websocket/__tests__/createConnectionTransport.test.ts +0 -49
  1013. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +0 -170
  1014. package/src/core/websocket/state.ts +0 -7
  1015. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  1016. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -335
  1017. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  1018. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  1019. package/src/core/websocket/transports/basic.ts +0 -102
  1020. package/src/core/websocket/transports/create.ts +0 -31
  1021. package/src/core/websocket/transports/sse.ts +0 -308
  1022. package/src/core/websocket/transports/transport.ts +0 -50
  1023. package/src/core/websocket/transports/ws.ts +0 -98
  1024. package/src/core/websocket/useNotebookClient.ts +0 -88
  1025. package/src/utils/__tests__/id-tree.test.ts +0 -2145
  1026. package/src/utils/id-tree.tsx +0 -1134
  1027. /package/src/{core/attachments/toast.ts → components/editor/agent-toast.ts} +0 -0
  1028. /package/src/core/{websocket → session}/connection-utils.ts +0 -0
@@ -1,32 +1,37 @@
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";
21
+ import {
22
+ notebookQueuedOrRunningCountAtom,
23
+ submitCells,
24
+ } from "@/core/executions";
25
+ import { store } from "@/core/state/jotai";
17
26
  import { MockNotebook } from "@/__mocks__/notebook";
18
27
  import { cellId } from "@/__tests__/branded";
19
28
  import type { CellHandle } from "@/components/editor/notebook-cell";
20
- import { CellId, SCRATCH_CELL_ID, SETUP_CELL_ID } from "@/core/cells/ids";
29
+ import { CellId, SCRATCH_CELL_ID } from "@/core/cells/ids";
21
30
  import { foldAllBulk, unfoldAllBulk } from "@/core/codemirror/editing/commands";
22
31
  import { adaptiveLanguageConfiguration } from "@/core/codemirror/language/extension";
23
32
  import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
24
33
  import type { OutputMessage } from "@/core/kernel/messages";
25
- import {
26
- type CollapsibleTree,
27
- MultiColumn,
28
- type CellColumnId,
29
- } from "@/utils/id-tree";
34
+ import type { CellConfig } from "@/core/network/types";
30
35
  import type { Seconds } from "@/utils/time";
31
36
  import {
32
37
  exportedForTesting,
@@ -34,13 +39,12 @@ import {
34
39
  type NotebookState,
35
40
  notebookAtom,
36
41
  } from "../cells";
37
- import { exportedForTesting as documentTransactionTestExports } from "../document-changes";
38
42
  import {
39
- focusAndScrollCellIntoView,
40
- scrollToBottom,
41
- scrollToTop,
42
- } from "../scrollCellIntoView";
43
- import type { CellData } from "../types";
43
+ type CellData,
44
+ createCell,
45
+ createCellConfig,
46
+ createCellRuntimeState,
47
+ } from "../types";
44
48
 
45
49
  vi.mock("@/core/codemirror/editing/commands", () => ({
46
50
  foldAllBulk: vi.fn(),
@@ -60,9 +64,6 @@ vi.mock("../scrollCellIntoView", async (importOriginal) => {
60
64
  };
61
65
  });
62
66
 
63
- const FIRST_COLUMN = 0;
64
- const SECOND_COLUMN = 1;
65
-
66
67
  const { initialNotebookState, reducer, createActions } = exportedForTesting;
67
68
 
68
69
  function formatCells(notebook: NotebookState) {
@@ -78,14 +79,7 @@ function formatCells(notebook: NotebookState) {
78
79
  const cells = cellsId.map((cellId) => cellData[cellId]);
79
80
  return cells.map((cell) => printCell(cell)).join("\n\n");
80
81
  };
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));
82
+ return wrap(printCells(cellIds));
89
83
  }
90
84
 
91
85
  function createEditor(content: string) {
@@ -96,6 +90,7 @@ function createEditor(content: string) {
96
90
  adaptiveLanguageConfiguration({
97
91
  cellId: cellId("cell1"),
98
92
  completionConfig: {
93
+ ...defaultUserConfig().completion,
99
94
  activate_on_typing: true,
100
95
  signature_hint_on_typing: false,
101
96
  copilot: false,
@@ -103,7 +98,7 @@ function createEditor(content: string) {
103
98
  },
104
99
  hotkeys: new OverridingHotkeyProvider({}),
105
100
  placeholderType: "marimo-import",
106
- lspConfig: {},
101
+ lspConfig: { ...defaultUserConfig().language_servers },
107
102
  }),
108
103
  ],
109
104
  });
@@ -121,8 +116,7 @@ describe("cell reducer", () => {
121
116
  let cells: ReturnType<typeof flattenTopLevelNotebookCells>;
122
117
  let firstCellId: CellId;
123
118
 
124
- const actions = createActions((action) => {
125
- state = reducer(state, action);
119
+ const mountEditors = () => {
126
120
  for (const [cellIdString, handle] of Object.entries(state.cellHandles)) {
127
121
  // @ts-expect-error - Typescript Object.entries doesn't know that keys are CellId
128
122
  const cid: CellId = cellIdString;
@@ -136,7 +130,83 @@ describe("cell reducer", () => {
136
130
  }
137
131
  }
138
132
  cells = flattenTopLevelNotebookCells(state);
139
- });
133
+ };
134
+ const actions = createActions((action) => {
135
+ state = reducer(state, action);
136
+ mountEditors();
137
+ });
138
+
139
+ /** Put a cell into the notebook the way the document would. */
140
+ function seedCell({
141
+ cellId,
142
+ before,
143
+ code = "",
144
+ name,
145
+ config,
146
+ newCellId,
147
+ hideCode,
148
+ autoFocus = true,
149
+ }: {
150
+ cellId: CellId | "__end__";
151
+ before: boolean;
152
+ code?: string;
153
+ name?: string;
154
+ config?: CellConfig;
155
+ newCellId?: CellId;
156
+ hideCode?: boolean;
157
+ autoFocus?: boolean;
158
+ }): CellId {
159
+ const cellIndex =
160
+ cellId === "__end__"
161
+ ? state.cellIds.length
162
+ : state.cellIds.indexOf(cellId);
163
+ const id = newCellId ?? CellId.create();
164
+ const cellIds = [...state.cellIds];
165
+ cellIds.splice(before ? cellIndex : cellIndex + 1, 0, id);
166
+ state = {
167
+ ...state,
168
+ cellIds,
169
+ cellData: {
170
+ ...state.cellData,
171
+ [id]: createCell({
172
+ id,
173
+ code,
174
+ name,
175
+ config: createCellConfig({
176
+ ...config,
177
+ ...(hideCode != null && { hide_code: hideCode }),
178
+ }),
179
+ }),
180
+ },
181
+ cellRuntime: { ...state.cellRuntime, [id]: createCellRuntimeState() },
182
+ cellHandles: { ...state.cellHandles, [id]: createRef() },
183
+ scrollKey: autoFocus ? id : null,
184
+ };
185
+ mountEditors();
186
+ return id;
187
+ }
188
+
189
+ /** Edit a cell's code as the document delivers it. */
190
+ function editCode(cellId: CellId, code: string) {
191
+ const cells = state.cellIds.map((id) => {
192
+ const cell = state.cellData[id];
193
+ return {
194
+ id,
195
+ code: id === cellId ? code : cell.code,
196
+ name: cell.name,
197
+ config: {
198
+ column: cell.config.column ?? null,
199
+ disabled: cell.config.disabled ?? false,
200
+ hide_code: cell.config.hide_code ?? false,
201
+ expand_output: cell.config.expand_output ?? false,
202
+ },
203
+ };
204
+ });
205
+ actions.reconcileDocument({
206
+ order: state.cellIds,
207
+ cells: new Map(cells.map((cell) => [cell.id, cell])),
208
+ });
209
+ }
140
210
 
141
211
  let i = 0;
142
212
  const originalCreate = CellId.create.bind(CellId);
@@ -151,852 +221,214 @@ describe("cell reducer", () => {
151
221
  i = 0;
152
222
 
153
223
  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();
224
+ seedCell({ cellId: "__end__", before: false });
225
+ firstCellId = state.cellIds[0];
161
226
  });
162
227
 
163
228
  afterAll(() => {
164
229
  CellId.create = originalCreate;
165
230
  });
166
231
 
167
- it("can add a cell after another cell", () => {
168
- actions.createNewCell({
169
- cellId: firstCellId,
170
- before: false,
171
- });
232
+ it("can update a cell", () => {
233
+ editCode(firstCellId, "import numpy as np");
172
234
  expect(formatCells(state)).toMatchInlineSnapshot(`
173
235
  "
174
- [0] ''
175
-
176
- [1] ''
236
+ [0] 'import numpy as np'
177
237
  "
178
238
  `);
179
239
  });
180
240
 
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,
241
+ it("keeps error evidence after an edit and a new queued run", () => {
242
+ const code = "raise ValueError('old failure')";
243
+ const output: OutputMessage = {
244
+ channel: "marimo-error",
245
+ mimetype: "application/vnd.marimo+error",
246
+ data: [
247
+ {
248
+ type: "exception",
249
+ msg: "old failure",
250
+ exception_type: "ValueError",
251
+ raising_cell: null,
252
+ traceback: "<pre>ValueError: old failure</pre>",
253
+ },
254
+ ],
255
+ code,
256
+ timestamp: 1,
257
+ };
258
+ actions.handleCellMessage({
259
+ cell_id: firstCellId,
260
+ output,
261
+ console: {
262
+ channel: "stderr",
263
+ mimetype: "application/vnd.marimo+traceback",
264
+ data: "<pre>ValueError: old failure</pre>",
265
+ code,
266
+ timestamp: 1,
267
+ },
268
+ status: "idle",
269
+ timestamp: 1,
186
270
  });
187
- expect(formatCells(state)).toMatchInlineSnapshot(`
188
- "
189
- [0] ''
271
+ editCode(firstCellId, "print('new code')");
272
+ actions.prepareForRun({ cellId: firstCellId });
273
+ actions.handleCellMessage({
274
+ cell_id: firstCellId,
275
+ output: null,
276
+ console: null,
277
+ status: "queued",
278
+ timestamp: 2,
279
+ });
280
+ const runtime = state.cellRuntime[firstCellId];
281
+ expect(runtime.output).toEqual(output);
282
+ expect(runtime.consoleOutputs[0].code).toBe(code);
283
+ expect(runtime.consoleOutputs[0].data).toContain("old failure");
284
+ });
190
285
 
191
- [1] 'import numpy as np'
192
- "
193
- `);
286
+ it("can run cell and receive cell messages", () => {
287
+ // HAPPY PATH
288
+ /////////////////
289
+ // Initial state
290
+ let cell = cells[0];
291
+ expect(cell).toMatchSnapshot("initial state");
194
292
 
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
- });
293
+ // Update code
294
+ editCode(firstCellId, "import marimo as mo");
295
+ cell = cells[0];
296
+ expect(cell).toMatchSnapshot("code edited");
200
297
 
201
- it("can add a cell before another cell", () => {
202
- actions.createNewCell({
298
+ // Prepare for run
299
+ actions.prepareForRun({
203
300
  cellId: firstCellId,
204
- before: true,
205
301
  });
206
- expect(formatCells(state)).toMatchInlineSnapshot(`
207
- "
208
- [1] ''
302
+ cell = cells[0];
303
+ expect(cell).toMatchSnapshot("prepared for run");
209
304
 
210
- [0] ''
211
- "
212
- `);
213
- });
305
+ // Receive queued messages
306
+ actions.handleCellMessage({
307
+ cell_id: firstCellId,
308
+ output: undefined,
309
+ console: null,
310
+ status: "queued",
311
+ timestamp: new Date(10).getTime() as Seconds,
312
+ });
313
+ cell = cells[0];
314
+ expect(cell).toMatchSnapshot("queued message received");
214
315
 
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 },
316
+ // Receive running messages
317
+ actions.handleCellMessage({
318
+ cell_id: firstCellId,
319
+ output: undefined,
320
+ console: null,
321
+ status: "running",
322
+ timestamp: new Date(20).getTime() as Seconds,
222
323
  });
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
- });
324
+ cell = cells[0];
325
+ expect(cell).toMatchSnapshot("running message received");
228
326
 
229
- it("can delete a Python cell and undo delete", () => {
230
- actions.createNewCell({
231
- cellId: firstCellId,
232
- before: false,
327
+ // Console messages shouldn't transition status
328
+ actions.handleCellMessage({
329
+ cell_id: firstCellId,
330
+ output: undefined,
331
+ console: {
332
+ channel: "stdout",
333
+ mimetype: "text/plain",
334
+ data: "hello!",
335
+ timestamp: 0,
336
+ },
337
+ status: undefined,
338
+ timestamp: new Date(22).getTime() as Seconds,
233
339
  });
234
- actions.deleteCell({
235
- cellId: firstCellId,
340
+ cell = cells[0];
341
+ expect(cell).toMatchSnapshot("console message received");
342
+
343
+ // Receive output messages
344
+ actions.handleCellMessage({
345
+ cell_id: firstCellId,
346
+ output: {
347
+ channel: "output",
348
+ mimetype: "text/plain",
349
+ data: "ok",
350
+ timestamp: 0,
351
+ },
352
+ console: {
353
+ channel: "stdout",
354
+ mimetype: "text/plain",
355
+ data: "hello!",
356
+ timestamp: 0,
357
+ },
358
+ status: "idle",
359
+ timestamp: new Date(33).getTime() as Seconds,
236
360
  });
237
- expect(formatCells(state)).toMatchInlineSnapshot(`
238
- "
239
- [1] ''
240
- "
241
- `);
361
+ cell = cells[0];
362
+ expect(cell).toMatchSnapshot("output received");
242
363
 
243
- // undo
244
- actions.undoDeleteCell();
245
- expect(formatCells(state)).toMatchInlineSnapshot(`
246
- "
247
- [2] ''
364
+ // EDITING BACK AND FORTH
365
+ /////////////////
366
+ // Update code again
367
+ editCode(firstCellId, "import marimo as mo\nimport numpy");
368
+ cell = cells[0];
369
+ expect(cell).toMatchSnapshot("code edited again");
248
370
 
249
- [1] ''
250
- "
251
- `);
371
+ // Update code should be unedited
372
+ editCode(firstCellId, "import marimo as mo");
373
+ cell = cells[0];
374
+ expect(cell).toMatchSnapshot("code reverted");
252
375
 
253
- // Verify scrollKey is set to the restored cell
254
- expect(state.scrollKey).toBe("2");
255
- });
376
+ // Update code should be edited again
377
+ editCode(firstCellId, "import marimo as mo\nimport numpy");
378
+ cell = cells[0];
379
+ expect(cell).toMatchSnapshot("code changed after revert");
256
380
 
257
- it("can delete a SQL cell and undo delete", () => {
258
- actions.createNewCell({
381
+ // ERROR RESPONSE
382
+ /////////////////
383
+ // Prepare for run
384
+ actions.prepareForRun({
259
385
  cellId: firstCellId,
260
- before: false,
261
- code: `df = mo.sql("""SELECT * FROM table""")`,
262
386
  });
263
- const newCellId = state.cellIds.getColumns()[0].atOrThrow(1);
264
- actions.deleteCell({
265
- cellId: newCellId,
387
+ cell = cells[0];
388
+ expect(cell.output).not.toBe(null); // keep old output
389
+ // Queue
390
+ actions.handleCellMessage({
391
+ cell_id: firstCellId,
392
+ output: undefined,
393
+ console: null,
394
+ status: "queued",
395
+ timestamp: new Date(40).getTime() as Seconds,
266
396
  });
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}""")`,
397
+ cell = cells[0];
398
+ expect(cell.output).not.toBe(null); // keep old output
399
+ // Running
400
+ actions.handleCellMessage({
401
+ cell_id: firstCellId,
402
+ output: undefined,
403
+ console: null,
404
+ status: "running",
405
+ timestamp: new Date(50).getTime() as Seconds,
290
406
  });
291
- const newCellId = state.cellIds.getColumns()[0].atOrThrow(1);
292
- actions.deleteCell({
293
- cellId: newCellId,
407
+ cell = cells[0];
408
+ expect(cell.output).not.toBe(null); // keep old output
409
+ // Receive error
410
+ actions.handleCellMessage({
411
+ cell_id: firstCellId,
412
+ output: {
413
+ channel: "marimo-error",
414
+ mimetype: "application/vnd.marimo+error",
415
+ data: [
416
+ { type: "exception", exception_type: "ValueError", msg: "Oh no!" },
417
+ ],
418
+ timestamp: 0,
419
+ },
420
+ console: null,
421
+ status: "idle",
422
+ timestamp: new Date(61).getTime() as Seconds,
294
423
  });
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
- });
424
+ cell = cells[0];
425
+ expect(cell.output).not.toBe(null); // keep old output
426
+ expect(cell).toMatchSnapshot("error response received");
311
427
 
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({
428
+ // INTERRUPT RESPONSE
429
+ /////////////////
430
+ // Prepare for run
431
+ actions.prepareForRun({
1000
432
  cellId: firstCellId,
1001
433
  });
1002
434
  cell = cells[0];
@@ -1007,7 +439,6 @@ describe("cell reducer", () => {
1007
439
  output: undefined,
1008
440
  console: null,
1009
441
  status: "queued",
1010
- stale_inputs: null,
1011
442
  timestamp: new Date(40).getTime() as Seconds,
1012
443
  });
1013
444
  cell = cells[0];
@@ -1018,62 +449,6 @@ describe("cell reducer", () => {
1018
449
  output: undefined,
1019
450
  console: null,
1020
451
  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({
1055
- cellId: firstCellId,
1056
- });
1057
- cell = cells[0];
1058
- expect(cell.output).not.toBe(null); // keep old output
1059
- // Queue
1060
- actions.handleCellMessage({
1061
- cell_id: firstCellId,
1062
- output: undefined,
1063
- console: null,
1064
- status: "queued",
1065
- stale_inputs: null,
1066
- timestamp: new Date(40).getTime() as Seconds,
1067
- });
1068
- cell = cells[0];
1069
- expect(cell.output).not.toBe(null); // keep old output
1070
- // Running
1071
- actions.handleCellMessage({
1072
- cell_id: firstCellId,
1073
- output: undefined,
1074
- console: null,
1075
- status: "running",
1076
- stale_inputs: null,
1077
452
  timestamp: new Date(50).getTime() as Seconds,
1078
453
  });
1079
454
  cell = cells[0];
@@ -1089,100 +464,23 @@ describe("cell reducer", () => {
1089
464
  },
1090
465
  console: null,
1091
466
  status: "idle",
1092
- stale_inputs: null,
1093
467
  timestamp: new Date(61).getTime() as Seconds,
1094
468
  });
1095
469
  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
470
  expect(cell).toMatchSnapshot("interruption response received");
1102
471
  });
1103
472
 
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
473
  it("can run a stale cell", () => {
1168
474
  // Update code of first cell
1169
- actions.updateCellCode({
1170
- cellId: firstCellId,
1171
- code: "import marimo as mo",
1172
- formattingChange: false,
1173
- });
475
+ editCode(firstCellId, "import marimo as mo");
1174
476
  // Add cell
1175
- actions.createNewCell({
477
+ seedCell({
1176
478
  cellId: firstCellId,
1177
479
  before: false,
1178
480
  });
1179
481
  const secondCell = cells[1].id;
1180
482
  // Update code
1181
- actions.updateCellCode({
1182
- code: "mo.slider()",
1183
- cellId: secondCell,
1184
- formattingChange: false,
1185
- });
483
+ editCode(secondCell, "mo.slider()");
1186
484
 
1187
485
  // Prepare for run
1188
486
  actions.prepareForRun({
@@ -1195,15 +493,9 @@ describe("cell reducer", () => {
1195
493
  output: undefined,
1196
494
  console: null,
1197
495
  status: "queued",
1198
- stale_inputs: null,
1199
496
  timestamp: new Date(10).getTime() as Seconds,
1200
497
  });
1201
498
  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
499
  expect(cell).toMatchSnapshot("queued message received");
1208
500
 
1209
501
  // Receive stale message
@@ -1212,105 +504,15 @@ describe("cell reducer", () => {
1212
504
  output: undefined,
1213
505
  console: null,
1214
506
  status: undefined,
1215
- stale_inputs: true,
1216
507
  timestamp: new Date(20).getTime() as Seconds,
1217
508
  });
1218
509
  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
510
  expect(cell).toMatchSnapshot("stale inputs received");
1225
511
  });
1226
512
 
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
513
  it("can run a stopped cell", () => {
1308
514
  // 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
- });
515
+ editCode(firstCellId, "mo.md('This has an ancestor that was stopped')");
1314
516
 
1315
517
  // Prepare for run
1316
518
  actions.prepareForRun({
@@ -1323,17 +525,9 @@ describe("cell reducer", () => {
1323
525
  output: undefined,
1324
526
  console: null,
1325
527
  status: "queued",
1326
- stale_inputs: null,
1327
528
  timestamp: new Date(10).getTime() as Seconds,
1328
529
  });
1329
530
  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
531
  expect(cell).toMatchSnapshot("queued message received");
1338
532
 
1339
533
  // Receive idle message
@@ -1342,7 +536,6 @@ describe("cell reducer", () => {
1342
536
  output: undefined,
1343
537
  console: null,
1344
538
  status: "idle",
1345
- stale_inputs: null,
1346
539
  timestamp: new Date(20).getTime() as Seconds,
1347
540
  });
1348
541
  cell = cells[0];
@@ -1365,16 +558,13 @@ describe("cell reducer", () => {
1365
558
  },
1366
559
  console: null,
1367
560
  status: "idle",
1368
- stale_inputs: null,
1369
561
  timestamp: new Date(20).getTime() as Seconds,
1370
562
  });
1371
563
  cell = cells[0];
1372
- expect(cell.status).toBe("idle");
1373
564
  // oxlint-disable-next-line typescript/no-explicit-any no-unsafe-optional-chaining
1374
565
  expect((cell.output?.data as any)[0].msg).toBe(
1375
566
  "This cell wasn't run because an ancestor was stopped with `mo.stop`: ",
1376
567
  );
1377
- expect(cell.stopped).toBe(true);
1378
568
  expect(cell).toMatchSnapshot("ancestor stopped");
1379
569
 
1380
570
  // Receive queued message
@@ -1383,12 +573,9 @@ describe("cell reducer", () => {
1383
573
  output: undefined,
1384
574
  console: null,
1385
575
  status: "queued",
1386
- stale_inputs: null,
1387
576
  timestamp: new Date(30).getTime() as Seconds,
1388
577
  });
1389
578
  cell = cells[0];
1390
- expect(cell.status).toBe("queued");
1391
- expect(cell.stopped).toBe(true);
1392
579
  expect(cell).toMatchSnapshot("queued after stop");
1393
580
 
1394
581
  // Receive running message
@@ -1397,12 +584,9 @@ describe("cell reducer", () => {
1397
584
  output: undefined,
1398
585
  console: null,
1399
586
  status: "running",
1400
- stale_inputs: null,
1401
587
  timestamp: new Date(40).getTime() as Seconds,
1402
588
  });
1403
589
  cell = cells[0];
1404
- expect(cell.status).toBe("running");
1405
- expect(cell.stopped).toBe(false);
1406
590
  expect(cell.output).toBe(null);
1407
591
  expect(cell).toMatchSnapshot("running after stop");
1408
592
  });
@@ -1429,7 +613,6 @@ describe("cell reducer", () => {
1429
613
 
1430
614
  // Initial state
1431
615
  let cell = cells[0];
1432
- expect(cell.status).toBe("idle");
1433
616
  expect(cell.consoleOutputs).toEqual([]);
1434
617
  expect(cell).toMatchSnapshot("initial state");
1435
618
 
@@ -1438,7 +621,6 @@ describe("cell reducer", () => {
1438
621
  cellId: firstCellId,
1439
622
  });
1440
623
  cell = cells[0];
1441
- expect(cell.status).toBe("queued");
1442
624
  expect(cell.consoleOutputs).toEqual([]);
1443
625
  expect(cell).toMatchSnapshot("prepared for run");
1444
626
 
@@ -1448,11 +630,9 @@ describe("cell reducer", () => {
1448
630
  output: undefined,
1449
631
  console: null,
1450
632
  status: "queued",
1451
- stale_inputs: null,
1452
633
  timestamp: new Date(10).getTime() as Seconds,
1453
634
  });
1454
635
  cell = cells[0];
1455
- expect(cell.status).toBe("queued");
1456
636
  expect(cell).toMatchSnapshot("queued message received");
1457
637
 
1458
638
  // Receive running messages
@@ -1461,11 +641,9 @@ describe("cell reducer", () => {
1461
641
  output: undefined,
1462
642
  console: null,
1463
643
  status: "running",
1464
- stale_inputs: null,
1465
644
  timestamp: new Date(20).getTime() as Seconds,
1466
645
  });
1467
646
  cell = cells[0];
1468
- expect(cell.status).toBe("running");
1469
647
  expect(cell).toMatchSnapshot("running message received");
1470
648
 
1471
649
  // Add console
@@ -1474,16 +652,13 @@ describe("cell reducer", () => {
1474
652
  output: undefined,
1475
653
  console: STDOUT,
1476
654
  status: undefined,
1477
- stale_inputs: null,
1478
655
  timestamp: new Date(22).getTime() as Seconds,
1479
656
  });
1480
657
  cell = cells[0];
1481
- expect(cell.status).toBe("running");
1482
658
  expect(cell).toMatchSnapshot("stdout received");
1483
659
 
1484
660
  cell = cells[0];
1485
661
  expect(cell.consoleOutputs[0]).toMatchObject(STDOUT);
1486
- expect(cell.status).toBe("running");
1487
662
  expect(cell).toMatchSnapshot("stdout verified");
1488
663
 
1489
664
  // Ask via stdin
@@ -1492,13 +667,11 @@ describe("cell reducer", () => {
1492
667
  output: undefined,
1493
668
  console: STD_IN_1,
1494
669
  status: undefined,
1495
- stale_inputs: null,
1496
670
  timestamp: new Date(22).getTime() as Seconds,
1497
671
  });
1498
672
  cell = cells[0];
1499
673
  expect(cell.consoleOutputs[0]).toMatchObject(STDOUT);
1500
674
  expect(cell.consoleOutputs[1]).toMatchObject(STD_IN_1);
1501
- expect(cell.status).toBe("running");
1502
675
  expect(cell).toMatchSnapshot("stdin requested");
1503
676
 
1504
677
  // Response to stdin
@@ -1512,7 +685,6 @@ describe("cell reducer", () => {
1512
685
  ...STD_IN_1,
1513
686
  response: "Marimo!",
1514
687
  });
1515
- expect(cell.status).toBe("running");
1516
688
  expect(cell).toMatchSnapshot("stdin answered");
1517
689
 
1518
690
  // Ask via stdin, again
@@ -1521,7 +693,6 @@ describe("cell reducer", () => {
1521
693
  output: undefined,
1522
694
  console: STD_IN_2,
1523
695
  status: undefined,
1524
- stale_inputs: null,
1525
696
  timestamp: new Date(22).getTime() as Seconds,
1526
697
  });
1527
698
  cell = cells[0];
@@ -1530,7 +701,6 @@ describe("cell reducer", () => {
1530
701
  { ...STD_IN_1, response: "Marimo!" },
1531
702
  STD_IN_2,
1532
703
  ]);
1533
- expect(cell.status).toBe("running");
1534
704
  expect(cell).toMatchSnapshot("second stdin requested");
1535
705
 
1536
706
  // Interrupt, so we respond with ""
@@ -1544,11 +714,9 @@ describe("cell reducer", () => {
1544
714
  },
1545
715
  console: null,
1546
716
  status: "idle",
1547
- stale_inputs: null,
1548
717
  timestamp: new Date(61).getTime() as Seconds,
1549
718
  });
1550
719
  cell = cells[0];
1551
- expect(cell.status).toBe("idle");
1552
720
  expect(cell.consoleOutputs).toMatchObject([
1553
721
  STDOUT,
1554
722
  { ...STD_IN_1, response: "Marimo!" },
@@ -1568,7 +736,6 @@ describe("cell reducer", () => {
1568
736
  output: undefined,
1569
737
  console: prompt,
1570
738
  status: "running",
1571
- stale_inputs: null,
1572
739
  timestamp: 1 as Seconds,
1573
740
  });
1574
741
 
@@ -1579,7 +746,9 @@ describe("cell reducer", () => {
1579
746
  response: secret,
1580
747
  });
1581
748
 
1582
- expect(cells[0].consoleOutputs).toEqual([{ ...prompt, response: "" }]);
749
+ expect(cells[0].consoleOutputs).toEqual([
750
+ { ...prompt, id: 1, response: "" },
751
+ ]);
1583
752
  expect(JSON.stringify(cells[0].consoleOutputs)).not.toContain(secret);
1584
753
  });
1585
754
 
@@ -1598,7 +767,6 @@ describe("cell reducer", () => {
1598
767
  output: undefined,
1599
768
  console: null,
1600
769
  status: "running",
1601
- stale_inputs: null,
1602
770
  timestamp: new Date(20).getTime() as Seconds,
1603
771
  });
1604
772
  actions.handleCellMessage({
@@ -1606,7 +774,6 @@ describe("cell reducer", () => {
1606
774
  output: undefined,
1607
775
  console: STDOUT,
1608
776
  status: undefined,
1609
- stale_inputs: null,
1610
777
  timestamp: new Date(22).getTime() as Seconds,
1611
778
  });
1612
779
 
@@ -1640,7 +807,6 @@ describe("cell reducer", () => {
1640
807
 
1641
808
  // Initial state
1642
809
  let cell = cells[0];
1643
- expect(cell.status).toBe("idle");
1644
810
  expect(cell.consoleOutputs).toEqual([]);
1645
811
  expect(cell).toMatchSnapshot("initial state");
1646
812
 
@@ -1650,7 +816,6 @@ describe("cell reducer", () => {
1650
816
  output: undefined,
1651
817
  console: [OLD_STDOUT],
1652
818
  status: undefined,
1653
- stale_inputs: null,
1654
819
  timestamp: new Date(1).getTime() as Seconds,
1655
820
  });
1656
821
 
@@ -1659,7 +824,6 @@ describe("cell reducer", () => {
1659
824
  cellId: firstCellId,
1660
825
  });
1661
826
  cell = cells[0];
1662
- expect(cell.status).toBe("queued");
1663
827
  expect(cell.consoleOutputs).toMatchObject([OLD_STDOUT]); // Old stays there until it starts running
1664
828
  expect(cell).toMatchSnapshot("prepared for run");
1665
829
 
@@ -1669,11 +833,9 @@ describe("cell reducer", () => {
1669
833
  output: undefined,
1670
834
  console: null,
1671
835
  status: "queued",
1672
- stale_inputs: null,
1673
836
  timestamp: new Date(10).getTime() as Seconds,
1674
837
  });
1675
838
  cell = cells[0];
1676
- expect(cell.status).toBe("queued");
1677
839
  expect(cell.consoleOutputs).toMatchObject([OLD_STDOUT]); // Old stays there until it starts running
1678
840
  expect(cell).toMatchSnapshot("queued message received");
1679
841
 
@@ -1683,11 +845,9 @@ describe("cell reducer", () => {
1683
845
  output: undefined,
1684
846
  console: [], // Backend sends an empty array to clearu
1685
847
  status: "running",
1686
- stale_inputs: null,
1687
848
  timestamp: new Date(20).getTime() as Seconds,
1688
849
  });
1689
850
  cell = cells[0];
1690
- expect(cell.status).toBe("running");
1691
851
  expect(cell.consoleOutputs).toMatchObject([]);
1692
852
  expect(cell).toMatchSnapshot("console cleared while running");
1693
853
 
@@ -1696,611 +856,39 @@ describe("cell reducer", () => {
1696
856
  cell_id: firstCellId,
1697
857
  output: undefined,
1698
858
  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);
859
+ status: "idle",
860
+ timestamp: new Date(22).getTime() as Seconds,
861
+ });
862
+ cell = cells[0];
863
+ expect(cell.consoleOutputs).toMatchObject([STDOUT]);
864
+ expect(cell).toMatchSnapshot("console received while idle");
2262
865
  });
2263
866
 
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 });
867
+ it("can fold and unfold all cells", () => {
868
+ actions.foldAll();
869
+ expect(foldAllBulk).toHaveBeenCalled();
2268
870
 
2269
- actions.showCellIfHidden({ cellId: cellId("1") });
2270
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(firstCellId)).toBe(
2271
- false,
2272
- );
871
+ actions.unfoldAll();
872
+ expect(unfoldAllBulk).toHaveBeenCalled();
2273
873
  });
2274
874
 
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");
875
+ it("can clear logs", () => {
876
+ state.cellLogs = [
877
+ {
878
+ level: "stderr",
879
+ message: "log1",
880
+ timestamp: 0,
881
+ source: { type: "cell", cellId: firstCellId },
882
+ },
883
+ {
884
+ level: "stderr",
885
+ message: "log1",
886
+ timestamp: 0,
887
+ source: { type: "cell", cellId: firstCellId },
888
+ },
889
+ ];
890
+ actions.clearLogs();
891
+ expect(state.cellLogs).toEqual([]);
2304
892
  });
2305
893
 
2306
894
  it("can handle multiple console outputs", () => {
@@ -2322,7 +910,6 @@ describe("cell reducer", () => {
2322
910
  output: undefined,
2323
911
  console: [STDOUT1, STDOUT2],
2324
912
  status: "running",
2325
- stale_inputs: null,
2326
913
  timestamp: new Date(20).getTime() as Seconds,
2327
914
  });
2328
915
 
@@ -2339,407 +926,6 @@ describe("cell reducer", () => {
2339
926
  `);
2340
927
  });
2341
928
 
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
929
  it("can clear output of a single cell", () => {
2744
930
  // Set up initial state with output
2745
931
  actions.handleCellMessage({
@@ -2757,7 +943,6 @@ describe("cell reducer", () => {
2757
943
  timestamp: 0,
2758
944
  },
2759
945
  status: "idle",
2760
- stale_inputs: null,
2761
946
  timestamp: new Date(33).getTime() as Seconds,
2762
947
  });
2763
948
 
@@ -2792,7 +977,6 @@ describe("cell reducer", () => {
2792
977
  timestamp: 0,
2793
978
  },
2794
979
  status: "idle",
2795
- stale_inputs: null,
2796
980
  timestamp: new Date(33).getTime() as Seconds,
2797
981
  });
2798
982
 
@@ -2806,7 +990,6 @@ describe("cell reducer", () => {
2806
990
  timestamp: 0,
2807
991
  },
2808
992
  status: "idle",
2809
- stale_inputs: null,
2810
993
  timestamp: new Date(34).getTime() as Seconds,
2811
994
  });
2812
995
 
@@ -2828,8 +1011,8 @@ describe("cell reducer", () => {
2828
1011
 
2829
1012
  it("can clear output of all cells", () => {
2830
1013
  // Create multiple cells with output
2831
- actions.createNewCell({ cellId: firstCellId, before: false });
2832
- const secondCellId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
1014
+ seedCell({ cellId: firstCellId, before: false });
1015
+ const secondCellId = state.cellIds[1];
2833
1016
 
2834
1017
  // Add output to both cells
2835
1018
  const outputMessage = {
@@ -2846,7 +1029,6 @@ describe("cell reducer", () => {
2846
1029
  timestamp: 0,
2847
1030
  },
2848
1031
  status: "idle",
2849
- stale_inputs: null,
2850
1032
  timestamp: new Date(33).getTime() as Seconds,
2851
1033
  } as const;
2852
1034
 
@@ -2854,142 +1036,30 @@ describe("cell reducer", () => {
2854
1036
  ...outputMessage,
2855
1037
  cell_id: firstCellId,
2856
1038
  });
2857
- actions.handleCellMessage({
2858
- ...outputMessage,
2859
- 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" });
1039
+ actions.handleCellMessage({
1040
+ ...outputMessage,
1041
+ cell_id: secondCellId,
1042
+ });
2975
1043
 
2976
- // Delete the setup cell
2977
- actions.deleteCell({ cellId: SETUP_CELL_ID });
1044
+ // Verify initial state has output
1045
+ expect(state.cellRuntime[firstCellId].output).not.toBeNull();
1046
+ expect(state.cellRuntime[firstCellId].consoleOutputs.length).toBe(1);
1047
+ expect(state.cellRuntime[secondCellId].output).not.toBeNull();
1048
+ expect(state.cellRuntime[secondCellId].consoleOutputs.length).toBe(1);
2978
1049
 
2979
- // Create a new setup cell
2980
- actions.addSetupCellIfDoesntExist({ code: "# New code" });
1050
+ // Clear all outputs
1051
+ actions.clearAllCellOutputs();
2981
1052
 
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);
1053
+ // Verify all outputs are cleared
1054
+ expect(state.cellRuntime[firstCellId].output).toBeNull();
1055
+ expect(state.cellRuntime[firstCellId].consoleOutputs).toEqual([]);
1056
+ expect(state.cellRuntime[secondCellId].output).toBeNull();
1057
+ expect(state.cellRuntime[secondCellId].consoleOutputs).toEqual([]);
2988
1058
  });
2989
1059
 
2990
1060
  it("can clear all outputs", () => {
2991
1061
  // Add a cell and give it output
2992
- actions.createNewCell({
1062
+ seedCell({
2993
1063
  cellId: firstCellId,
2994
1064
  before: false,
2995
1065
  });
@@ -3017,522 +1087,13 @@ describe("cell reducer", () => {
3017
1087
  expect(state.cellRuntime[cell2Id].output).toBeNull();
3018
1088
  });
3019
1089
 
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
1090
  describe("releaseCellAtoms", () => {
3530
1091
  it("atom families cache atoms until explicitly removed", () => {
3531
1092
  const { cellDataAtom, cellRuntimeAtom, cellHandleAtom } =
3532
1093
  exportedForTesting;
3533
1094
 
3534
- actions.createNewCell({ cellId: firstCellId, before: false });
3535
- const newCellId = state.cellIds.inOrderIds[1];
1095
+ seedCell({ cellId: firstCellId, before: false });
1096
+ const newCellId = state.cellIds[1];
3536
1097
 
3537
1098
  const dataAtom1 = cellDataAtom(newCellId);
3538
1099
  const runtimeAtom1 = cellRuntimeAtom(newCellId);
@@ -3547,116 +1108,15 @@ describe("cell reducer", () => {
3547
1108
  expect(runtimeAtom2).toBe(runtimeAtom1);
3548
1109
  expect(handleAtom2).toBe(handleAtom1);
3549
1110
  });
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
1111
  });
1112
+ it("withdraws the submission of a cell that leaves the document", () => {
1113
+ MockExecutions.seed(store, []);
1114
+ submitCells(store, [firstCellId]);
1115
+ expect(store.get(notebookQueuedOrRunningCountAtom)).toBe(1);
3641
1116
 
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
- };
1117
+ actions.reconcileDocument({ order: [], cells: new Map() });
3656
1118
 
3657
- expect(exportedForTesting.isCellCodeHidden(testState, testCellId)).toBe(
3658
- false,
3659
- );
1119
+ expect(store.get(notebookQueuedOrRunningCountAtom)).toBe(0);
3660
1120
  });
3661
1121
  });
3662
1122
 
@@ -3839,11 +1299,11 @@ describe("createTracebackInfoAtom", () => {
3839
1299
  data: [{ type: "syntax", msg: "Syntax error", lineno: 1 }],
3840
1300
  mimetype: "application/vnd.marimo+error",
3841
1301
  },
3842
- status: "queued",
3843
1302
  },
3844
1303
  },
3845
1304
  }),
3846
1305
  );
1306
+ MockExecutions.seed(store, [MockExecutions.queued(cellId("cell1"))]);
3847
1307
 
3848
1308
  const tracebackAtom = exportedForTesting.createTracebackInfoAtom(
3849
1309
  cellId("cell1"),
@@ -3871,11 +1331,11 @@ describe("createTracebackInfoAtom", () => {
3871
1331
  data: [{ type: "syntax", msg: "Syntax error", lineno: 1 }],
3872
1332
  mimetype: "application/vnd.marimo+error",
3873
1333
  },
3874
- status: "running",
3875
1334
  },
3876
1335
  },
3877
1336
  }),
3878
1337
  );
1338
+ MockExecutions.seed(store, [MockExecutions.running(cellId("cell1"))]);
3879
1339
 
3880
1340
  const tracebackAtom = exportedForTesting.createTracebackInfoAtom(
3881
1341
  cellId("cell1"),
@@ -3886,31 +1346,9 @@ describe("createTracebackInfoAtom", () => {
3886
1346
  });
3887
1347
  });
3888
1348
 
3889
- describe("setCells snapshot preservation", () => {
1349
+ describe("kernel-ready", () => {
3890
1350
  const CELL_A = cellId("A");
3891
1351
  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
1352
 
3915
1353
  const hydratedState = () =>
3916
1354
  MockNotebook.notebookState({
@@ -3940,44 +1378,74 @@ describe("setCells snapshot preservation", () => {
3940
1378
  },
3941
1379
  });
3942
1380
 
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", () => {
1381
+ it("resetCellRuntimes starts every cell's runtime over", () => {
3949
1382
  const next = exportedForTesting.reducer(hydratedState(), {
3950
- type: "setCells",
3951
- payload: newCells,
1383
+ type: "resetCellRuntimes",
1384
+ payload: undefined,
3952
1385
  });
3953
1386
 
3954
- expect(next.cellRuntime[CELL_A].output).toMatchObject({
3955
- data: "hydrated-A",
1387
+ expect(next.cellRuntime[CELL_A].output).toBeNull();
1388
+ expect(next.cellRuntime[CELL_B].consoleOutputs).toEqual([]);
1389
+ });
1390
+
1391
+ it("resetCellRuntimes starts the scratch cell over too", () => {
1392
+ const state = MockNotebook.notebookState({
1393
+ cellData: { [CELL_A]: { id: CELL_A, code: "1" } },
1394
+ cellRuntime: {
1395
+ [SCRATCH_CELL_ID]: {
1396
+ output: {
1397
+ channel: "output",
1398
+ mimetype: "text/plain",
1399
+ data: "scratch",
1400
+ timestamp: 0,
1401
+ },
1402
+ },
1403
+ },
3956
1404
  });
1405
+ const next = exportedForTesting.reducer(state, {
1406
+ type: "resetCellRuntimes",
1407
+ payload: undefined,
1408
+ });
1409
+
1410
+ expect(next.cellRuntime[SCRATCH_CELL_ID].output).toBeNull();
3957
1411
  });
3958
1412
 
3959
- it("preserves console-only hydration in WASM", () => {
3960
- const next = exportedForTesting.reducer(hydratedState(), {
3961
- type: "setCells",
3962
- payload: newCells,
1413
+ describe("resetCellRuntimes in WASM", () => {
1414
+ beforeEach(async () => {
1415
+ const { isWasm } = await import("@/core/wasm/utils");
1416
+ vi.mocked(isWasm).mockReturnValue(true);
3963
1417
  });
3964
1418
 
3965
- expect(next.cellRuntime[CELL_B].consoleOutputs).toHaveLength(1);
3966
- expect(next.cellRuntime[CELL_B].consoleOutputs[0]).toMatchObject({
3967
- data: "hydrated-B-stdout",
1419
+ afterEach(async () => {
1420
+ const { isWasm } = await import("@/core/wasm/utils");
1421
+ vi.mocked(isWasm).mockReturnValue(false);
3968
1422
  });
3969
- });
3970
1423
 
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,
1424
+ it("keeps hydrated outputs and console", () => {
1425
+ const next = exportedForTesting.reducer(hydratedState(), {
1426
+ type: "resetCellRuntimes",
1427
+ payload: undefined,
1428
+ });
1429
+
1430
+ expect(next.cellRuntime[CELL_A].output).toMatchObject({
1431
+ data: "hydrated-A",
1432
+ });
1433
+ expect(next.cellRuntime[CELL_B].consoleOutputs[0]).toMatchObject({
1434
+ data: "hydrated-B-stdout",
1435
+ });
3976
1436
  });
3977
1437
 
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([]);
1438
+ it("starts cells with no prior runtime over", () => {
1439
+ const state = MockNotebook.notebookState({
1440
+ cellData: { [CELL_A]: { id: CELL_A, code: "1" } },
1441
+ });
1442
+ const next = exportedForTesting.reducer(state, {
1443
+ type: "resetCellRuntimes",
1444
+ payload: undefined,
1445
+ });
1446
+
1447
+ expect(next.cellRuntime[CELL_A].output).toBeNull();
1448
+ expect(next.cellRuntime[CELL_A].consoleOutputs).toEqual([]);
1449
+ });
3982
1450
  });
3983
1451
  });