@marimo-team/islands 0.25.3-dev3 → 0.25.3-dev301

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 (1029) hide show
  1. package/dist/{ConnectedDataExplorerComponent-CNSxdKyJ.js → ConnectedDataExplorerComponent-X-_Gsw0B.js} +153 -177
  2. package/dist/{ErrorBoundary-iHE9DnZC.js → ErrorBoundary-D2XuUNty.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-DQDjNXt3.js → abnfDiagram-VCTEODGH-B8nNWmL7.js} +5 -5
  7. package/dist/any-language-editor-DhAtCzlx.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-Nmls7Wxn.js → architectureDiagram-5GKGNRK7-DcfTzRDz.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-De6m1Aro.js +507 -0
  16. package/dist/assets/__vite-browser-external-BzfoKXxV.js +1 -0
  17. package/dist/assets/worker-DVG30pr3.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-BDpymeoe.js → chat-ui-CtbON6Zl.js} +1712 -1440
  25. package/dist/{check-EDwSwP89.js → check-C7fmwkwt.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-CoIFEo-1.js → chunk-IMKFNOWR-BHJ3ZxAy.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-lHiGx3ZC.js → chunk-SHT3W25Y-CkOPJgVC.js} +10 -10
  40. package/dist/{chunk-SVP7TREG-BvsHIrY_.js → chunk-SVP7TREG-rDd35EZ7.js} +3 -3
  41. package/dist/{chunk-TICWLB2K-BC8YIm_n.js → chunk-TICWLB2K-uKdr5PNy.js} +10 -10
  42. package/dist/{chunk-XXDRQBXY-Dnl5VBqG.js → chunk-XXDRQBXY-BM-YIt-_.js} +1 -1
  43. package/dist/{chunk-YOKDWASO-DPZk8vhS.js → chunk-YOKDWASO-KxncHymC.js} +171 -396
  44. package/dist/classDiagram-ZZMXUADV-DctWoMRF.js +30 -0
  45. package/dist/classDiagram-v2-VYDZK3BY-DyanDjSq.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-Y0zfGGT1.js +131 -0
  51. package/dist/{coffeescript-C-eFwH5N.js → coffeescript-D6UgrGgo.js} +1 -1
  52. package/dist/{common-BrBUdtPy.js → common-B_rBSwAA.js} +10980 -10178
  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/{toDate-BNJf01u1.js → config-C4oddKcw.js} +107 -177
  56. package/dist/{copy-icon-Ft7t9bqz.js → copy-icon-CVrC6aa6.js} +21 -22
  57. package/dist/{cose-bilkent-JH36ORCC-_LyRKVqm.js → cose-bilkent-JH36ORCC-B_Oz4vMP.js} +58 -71
  58. package/dist/createReducer-DzlYBqjn.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-CgWra7wt.js → cynefinDiagram-5FMLGOSQ-uEhVcLHR.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-CFZ8-35c.js → dagre-GXQ25YYZ-B_NBKhOr.js} +14 -16
  69. package/dist/{data-grid-overlay-editor-Ch2SgmvG.js → data-grid-overlay-editor-04SWN9ol.js} +8 -8
  70. package/dist/{data-grid-types-BH_r7g7l.js → data-grid-types-CSElxhxo.js} +2 -2
  71. package/dist/{debounce-C3jyATpc.js → debounce-DrhhziJv.js} +2 -2
  72. package/dist/{diagram-S7CK7UJ4-lCNQrW7d.js → diagram-S7CK7UJ4-BQr1iw91.js} +7 -15
  73. package/dist/{diagram-UQ7AKVKN-CQPQ6pP_.js → diagram-UQ7AKVKN-DsAjdQ2j.js} +5 -5
  74. package/dist/{diagram-VSXAHHWV-BJWcVVJY.js → diagram-VSXAHHWV-CodTjaL3.js} +6 -6
  75. package/dist/{diagram-VX7I27RA-Cf3b0llz.js → diagram-VX7I27RA-0VAUrHmz.js} +7 -7
  76. package/dist/{diagram-Z3DM3KII-Bt4QLrpn.js → diagram-Z3DM3KII-C1aEuADo.js} +5 -5
  77. package/dist/{dialog-CEBFZNyc.js → dialog-U3stKkkL.js} +35 -35
  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-DpBpA_n1.js → dist-Du4PgaYp.js} +5 -5
  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-CP_qCYHC.js → dropdown-menu-BMtxnyHc.js} +2798 -1494
  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-D3NAyPFY.js → ebnfDiagram-PWID7BFC-oBie_-vi.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-Tlu09_Nm.js → erDiagram-RLTQ6QDP-DtWDCiaw.js} +14 -14
  114. package/dist/{erlang-AdO1-xtV.js → erlang-2KpKpfC7.js} +1 -1
  115. package/dist/{error-banner-Da600cwz.js → error-banner-CfIck83X.js} +94 -89
  116. package/dist/error-entries-CjsQY6ym.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-DxXNcC6q.js → feedback-button-DoriP8b7.js} +116 -126
  123. package/dist/flowDiagram-HODETNUW-EXK7bg_t.js +15 -0
  124. package/dist/{formats-BGh2thRc.js → formats-G0-mQsFy.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-BQT16JSc.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-BQ7uKbeo.js → glide-data-editor-BU_jOTDv.js} +6663 -2919
  133. package/dist/{glide-utils-DJyqh0EM.js → glide-utils-BkcGyXLQ.js} +109 -78
  134. package/dist/{globals-DSavh2ZB.js → globals-DziKJCFY.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-zwjq21tf.js → highlighted-body-KPVGNVTW-XbLtWlYz.js} +5 -5
  141. package/dist/{cells-C8iCYvLU.js → html-to-image-CIm3PqDi.js} +26098 -26387
  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-XrDxhr0x.js → infoDiagram-27XIBGKW-N4ajFTXv.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-CD1muzov.js → ishikawaDiagram-5VMMS53U-Bb2itmv5.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-Dg9W142_.js → kanban-definition-UXKFOSKX-DWfkIEDX.js} +10 -10
  153. package/dist/{katex-BIdZGsyO.js → katex-BmPSeP5t.js} +15 -24
  154. package/dist/linear-BhKepphY.js +251 -0
  155. package/dist/{livescript-CVPfYnY_.js → livescript-gIdRPKtg.js} +1 -1
  156. package/dist/{loader-BQOfjTIJ.js → loader-BIlwkY0d.js} +12 -12
  157. package/dist/{lua-4Fa-T-qq.js → lua-CxFpWvnv.js} +2 -2
  158. package/dist/main.js +10300 -8421
  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-CiAXjLpA.js → mermaid-BEBd0PHi.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-DgCidgYJ.js → mindmap-definition-YA3MSWOX-CmGSiRxU.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-D6kYaqh5.js → number-overlay-editor-BaZBWVbO.js} +16 -21
  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-coOYYKB6.js → pegDiagram-XKGWAZYB-dXflGNi0.js} +5 -5
  185. package/dist/pencil-BGy5pfXo.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-BnXTc3bj.js → pieDiagram-E7YTZNPT-Bl9G_NNa.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/precisionRound-DhAzeRcm.js +49 -0
  193. package/dist/{process-output-e4jj9Ijt.js → process-output-DAVapIAQ.js} +6 -6
  194. package/dist/{properties-Q-rkxld0.js → properties-Om0IDE07.js} +1 -1
  195. package/dist/{protobuf-DKyWZbs7.js → protobuf-B8nxxb4Q.js} +1 -1
  196. package/dist/{pug-42ZJUbEC.js → pug-lkUBPvVS.js} +3 -7
  197. package/dist/{puppet-B4UozHhh.js → puppet-DMkXsi4d.js} +1 -1
  198. package/dist/purify.es-DmnrIjg4.js +877 -0
  199. package/dist/{python-DREjAV__.js → python-JQVycMgr.js} +1 -1
  200. package/dist/{q-Bi63IpBO.js → q-DqE__nNB.js} +1 -1
  201. package/dist/{quadrantDiagram-AXDQQJYC-Ccw0xMjN.js → quadrantDiagram-AXDQQJYC-DLJJzlPi.js} +3 -3
  202. package/dist/{r-DSwXWKMR.js → r-Cbliap7Q.js} +1 -1
  203. package/dist/radar-RG4KPBEZ-9PwdCvai.js +2 -0
  204. package/dist/railroad-74A4TZTK-8N9kF5jl.js +2 -0
  205. package/dist/railroad-abnf-HS5TGJTU-Sg6YmzlR.js +2 -0
  206. package/dist/railroad-ebnf-LZEXJU2U-CYwp2Oee.js +2 -0
  207. package/dist/railroad-peg-WCYAUIDC-BfDkAuQ3.js +2 -0
  208. package/dist/{railroadDiagram-O6MQD6OU-B28-u83n.js → railroadDiagram-O6MQD6OU-Dg7GUHj4.js} +5 -5
  209. package/dist/{react-sHtZdjeP.js → react-CHbZqgBF.js} +1 -1
  210. package/dist/{react-dom-DUmH0GXv.js → react-dom-DhWGW7e0.js} +3 -3
  211. package/dist/{react-vega-BED0DjiB.js → react-vega-DVr24mNk.js} +13653 -13722
  212. package/dist/{requirementDiagram-BXWQKSXE-CdYCTMu0.js → requirementDiagram-BXWQKSXE-JNZB3XKK.js} +13 -13
  213. package/dist/{reveal-component-BTBfJ3ju.js → reveal-component-vRQ0ZbEq.js} +1171 -1284
  214. package/dist/{rolldown-runtime-Cb76FRpK.js → rolldown-runtime-BPCzCO3I.js} +1 -1
  215. package/dist/{rpm-DPb1DeKM.js → rpm-DmT_LNpE.js} +1 -1
  216. package/dist/{ruby-U7fZA4DP.js → ruby-BHVZTtLW.js} +1 -1
  217. package/dist/{sankeyDiagram-P5KCCOFB-ELvOcnZH.js → sankeyDiagram-P5KCCOFB-Bd7wvmbF.js} +2 -2
  218. package/dist/{sas-CLk1JpTn.js → sas-CrG5N1he.js} +1 -1
  219. package/dist/{scheme-CT-2k2Tl.js → scheme-BHUnz-IB.js} +1 -1
  220. package/dist/select-Ba_tMQWZ.js +1200 -0
  221. package/dist/{semaphore-89jsoHtS.js → semaphore-8KhQouXt.js} +4 -5
  222. package/dist/{sequenceDiagram-WJ2MYXX4-CM3aRAe_.js → sequenceDiagram-WJ2MYXX4-DekbwWOI.js} +6 -6
  223. package/dist/{shell-BLO7fcsk.js → shell-C5IBmRU5.js} +1 -1
  224. package/dist/{sieve-nG3C7WNH.js → sieve-BzCnt_E6.js} +2 -2
  225. package/dist/{sizeCapture-INFHLROL-Dx5SpkQF.js → sizeCapture-INFHLROL-DBeZ20mI.js} +1 -1
  226. package/dist/{smalltalk-BROcZybA.js → smalltalk-DQFAQul3.js} +1 -1
  227. package/dist/{solr-mVltHNc5.js → solr-BiapYPCk.js} +1 -1
  228. package/dist/{sparql-Cv1Ksr4v.js → sparql-BcUKO1is.js} +1 -1
  229. package/dist/{spec-BwarBpUJ.js → spec-CN3OxHz7.js} +42 -12
  230. package/dist/{src-CA_m4ONl.js → src-D5uZaZib.js} +8 -14
  231. package/dist/{stateDiagram-D77RDMKH-DOZUMVj8.js → stateDiagram-D77RDMKH-COzvSaN-.js} +18 -18
  232. package/dist/stateDiagram-v2-MP3YSRHH-rCvxZ0L8.js +30 -0
  233. package/dist/static-state-BJdfR8Bn.js +68 -0
  234. package/dist/{stex-Btf3omL8.js → stex-CQ_aCGTF.js} +1 -1
  235. package/dist/style.css +1 -1
  236. package/dist/{stylus-Dt4VvLXL.js → stylus-D4gHF5lg.js} +1 -1
  237. package/dist/{swift-DHsn_CVI.js → swift-CnvIBnbt.js} +1 -1
  238. package/dist/{swimlanes-42K2YHIH-BkSppxp8.js → swimlanes-42K2YHIH-DX2mMlXc.js} +37 -59
  239. package/dist/swimlanesDiagram-VR7AAH4N-DswSPZ5e.js +30 -0
  240. package/dist/{swiper-component-DKntyn8p.js → swiper-component-FBPvIqLf.js} +45 -78
  241. package/dist/{tcl-CV0OLBOl.js → tcl-DkOjjZYO.js} +1 -1
  242. package/dist/{textile-D320oKVM.js → textile-DgzBVSEW.js} +1 -1
  243. package/dist/{tiki-BF2aDPlB.js → tiki-MighF8A_.js} +1 -1
  244. package/dist/{time-BieKwdYS.js → time-Cc-G3e31.js} +19 -19
  245. package/dist/{timeline-definition-24CTP7MA-CaMQayqK.js → timeline-definition-24CTP7MA-DAT9AlwV.js} +5 -5
  246. package/dist/toast-BRd_eTIj.js +491 -0
  247. package/dist/{toml-CaUN7UHo.js → toml-B0B7UAba.js} +1 -1
  248. package/dist/{tooltip-BO1REbqs.js → tooltip-CHvXUQVs.js} +332 -308
  249. package/dist/treeView-Q6P3EWNA-BQywp0E1.js +2 -0
  250. package/dist/treemap-WGGIJYW6-P2ZJnldw.js +2 -0
  251. package/dist/{troff-DQUFcClZ.js → troff-B4IRX71y.js} +1 -1
  252. package/dist/{ttcn-CQL1duJS.js → ttcn-B4tpvdJD.js} +1 -1
  253. package/dist/{ttcn-cfg-BMmEFJPu.js → ttcn-cfg-pfE-1FV8.js} +1 -1
  254. package/dist/{turtle-C3esgsRr.js → turtle-Bc97ufp6.js} +1 -1
  255. package/dist/use-restore-focus-Ckq0jQzc.js +7534 -0
  256. package/dist/{use-vega-container-remeasure-Bz4PZMtt.js → use-vega-container-remeasure-DFBTESqc.js} +4 -8
  257. package/dist/{useAsyncData-Deh5kszT.js → useAsyncData-DMBliR6D.js} +21 -20
  258. package/dist/{useDateFormatter-D8XruNGG.js → useDateFormatter-CIPe17AG.js} +4 -4
  259. package/dist/{useDeepCompareMemoize-DXwzRINI.js → useDeepCompareMemoize-Kg-7A4b-.js} +3 -3
  260. package/dist/useEvent-xDnF2X04.js +19 -0
  261. package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities--px_wpZL.js} +2 -2
  262. package/dist/useLifecycle-BKVRcyGu.js +112 -0
  263. package/dist/{vb-RK1eDXRl.js → vb-C5kpOY_q.js} +1 -1
  264. package/dist/{vbscript-BCOeM0de.js → vbscript-Ck0hgOg5.js} +1 -1
  265. package/dist/{vega-component-DG29TAB7.js → vega-component-C23pVuni.js} +167 -172
  266. package/dist/{vega-embed-container-BNHE18G9.js → vega-embed-container-DAT8OHp9.js} +7 -7
  267. package/dist/{vega-loader.browser-DNST6uVf.js → vega-loader.browser-Cnf7IHA8.js} +331 -543
  268. package/dist/{velocity-CZKOFbOI.js → velocity-2Doc6dgJ.js} +1 -1
  269. package/dist/{vennDiagram-4TSXK5OY-CF8pfaK3.js → vennDiagram-4TSXK5OY-E7CMtY2M.js} +4 -4
  270. package/dist/{verilog-DqQTzVn8.js → verilog-CmZmYe4R.js} +2 -2
  271. package/dist/{vhdl-vQr2bfmi.js → vhdl-esMnUHZm.js} +1 -1
  272. package/dist/wardley-WFR3VGLG-CIBJ6O2G.js +2 -0
  273. package/dist/{wardleyDiagram-VM6X3IG4-dGPu39d-.js → wardleyDiagram-VM6X3IG4-BjtOazpV.js} +5 -5
  274. package/dist/{webidl-DzX7N40e.js → webidl-G_RlXG7G.js} +1 -1
  275. package/dist/{xquery-Cw_Q_hGR.js → xquery-IUQpMoMl.js} +1 -1
  276. package/dist/{xychartDiagram-S5SC5T6Z-CP6-9q56.js → xychartDiagram-S5SC5T6Z-DC09jtph.js} +8 -8
  277. package/dist/{yacas-DiC4jJBE.js → yacas-ObL0t2h9.js} +1 -1
  278. package/dist/{z80-CUOQDEJO.js → z80-jyuJhy0k.js} +1 -1
  279. package/package.json +18 -21
  280. package/src/__mocks__/document.tsx +63 -0
  281. package/src/__mocks__/executions.ts +197 -0
  282. package/src/__mocks__/notebook.ts +3 -7
  283. package/src/__mocks__/partial.ts +26 -0
  284. package/src/__mocks__/requests.ts +1 -4
  285. package/src/__tests__/CellStatus.test.tsx +25 -26
  286. package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +4 -3
  287. package/src/__tests__/main.test.tsx +24 -1
  288. package/src/__tests__/module-entry.test.ts +44 -0
  289. package/src/__tests__/mount.test.ts +9 -9
  290. package/src/__tests__/{host-imports.test.ts → replica-imports.test.ts} +12 -11
  291. package/src/components/ai/__tests__/ai-model-dropdown.test.tsx +63 -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 +156 -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 +112 -113
  299. package/src/components/app-config/mcp-config.tsx +30 -53
  300. package/src/components/app-config/user-config-form.css +91 -0
  301. package/src/components/app-config/user-config-form.tsx +266 -349
  302. package/src/components/chat/__tests__/capabilities-popover.test.tsx +0 -1
  303. package/src/components/chat/__tests__/chat-display.test.tsx +196 -0
  304. package/src/components/chat/__tests__/chat-markdown.test.tsx +43 -0
  305. package/src/components/chat/__tests__/chat-utils.test.ts +93 -1
  306. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  307. package/src/components/chat/__tests__/notebook-access-selector.test.tsx +64 -0
  308. package/src/components/chat/__tests__/turn-timing.test.ts +84 -0
  309. package/src/components/chat/__tests__/use-assistant-turn.test.ts +95 -0
  310. package/src/components/chat/__tests__/user-message.test.tsx +27 -0
  311. package/src/components/chat/acp/__tests__/agent-panel-initialization.test.tsx +1 -1
  312. package/src/components/chat/acp/__tests__/agent-panel.test.tsx +22 -17
  313. package/src/components/chat/acp/__tests__/session-tabs.test.tsx +43 -0
  314. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  315. package/src/components/chat/acp/agent-panel.tsx +6 -3
  316. package/src/components/chat/acp/blocks.tsx +13 -12
  317. package/src/components/chat/acp/common.tsx +4 -5
  318. package/src/components/chat/acp/model-selector.tsx +1 -1
  319. package/src/components/chat/acp/session-tabs.tsx +8 -3
  320. package/src/components/chat/agent-activity.tsx +132 -0
  321. package/src/components/chat/capabilities-popover.tsx +2 -11
  322. package/src/components/chat/chat-components.tsx +9 -4
  323. package/src/components/chat/chat-display.tsx +131 -57
  324. package/src/components/chat/chat-history-popover.tsx +6 -1
  325. package/src/components/chat/chat-markdown.tsx +15 -0
  326. package/src/components/chat/chat-panel.tsx +216 -294
  327. package/src/components/chat/chat-typography.css +108 -0
  328. package/src/components/chat/chat-utils.ts +24 -35
  329. package/src/components/chat/notebook-access-selector.stories.tsx +22 -0
  330. package/src/components/chat/notebook-access-selector.tsx +100 -0
  331. package/src/components/chat/tool-call/__tests__/tool-labels.test.ts +76 -0
  332. package/src/components/chat/tool-call/__tests__/tool-step.test.tsx +57 -0
  333. package/src/components/chat/tool-call/shared.ts +6 -0
  334. package/src/components/chat/tool-call/tool-call-view.tsx +22 -76
  335. package/src/components/chat/tool-call/tool-labels.ts +172 -0
  336. package/src/components/chat/tool-call/tool-result.tsx +1 -1
  337. package/src/components/chat/tool-call/tool-step.tsx +145 -0
  338. package/src/components/chat/turn-timing.ts +73 -0
  339. package/src/components/chat/use-assistant-turn.ts +65 -0
  340. package/src/components/chat/use-chat-generation.ts +20 -0
  341. package/src/components/chat/user-message.tsx +145 -0
  342. package/src/components/data-table/TableBottomBar.tsx +4 -1
  343. package/src/components/data-table/TableTopBar.tsx +2 -0
  344. package/src/components/data-table/__tests__/chart-spec-model.test.ts +195 -195
  345. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  346. package/src/components/data-table/__tests__/columns.test.tsx +105 -0
  347. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  348. package/src/components/data-table/__tests__/context-menu.test.ts +176 -0
  349. package/src/components/data-table/__tests__/data-table.test.tsx +1 -0
  350. package/src/components/data-table/__tests__/date-filter-inputs.test.tsx +24 -1
  351. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  352. package/src/components/data-table/__tests__/export-actions.test.tsx +434 -6
  353. package/src/components/data-table/__tests__/keyboard-selection.test.tsx +188 -0
  354. package/src/components/data-table/__tests__/pagination.test.tsx +28 -1
  355. package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
  356. package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
  357. package/src/components/data-table/cell-selection/feature.ts +2 -2
  358. package/src/components/data-table/cell-selection/types.ts +2 -2
  359. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
  360. package/src/components/data-table/charts/__tests__/spec-snapshot.test.ts +20 -20
  361. package/src/components/data-table/charts/__tests__/spec.test.ts +78 -52
  362. package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
  363. package/src/components/data-table/charts/chart-spec/spec.ts +68 -43
  364. package/src/components/data-table/charts/charts.tsx +40 -30
  365. package/src/components/data-table/charts/storage.ts +16 -5
  366. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  367. package/src/components/data-table/column-formatting/types.ts +1 -2
  368. package/src/components/data-table/column-summary/chart-spec-model.tsx +23 -16
  369. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  370. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  371. package/src/components/data-table/columns.tsx +45 -46
  372. package/src/components/data-table/context-menu.tsx +161 -46
  373. package/src/components/data-table/data-table.tsx +17 -1
  374. package/src/components/data-table/date-filter-inputs.tsx +15 -42
  375. package/src/components/data-table/date-popover.tsx +6 -4
  376. package/src/components/data-table/export-actions.tsx +267 -163
  377. package/src/components/data-table/pagination.tsx +44 -33
  378. package/src/components/data-table/range-focus/atoms.ts +4 -6
  379. package/src/components/data-table/range-focus/use-cell-range-selection.ts +65 -38
  380. package/src/components/data-table/range-focus/use-scroll-into-view.ts +21 -2
  381. package/src/components/data-table/renderers.tsx +16 -7
  382. package/src/components/data-table/row-viewer-panel/__tests__/row-viewer.test.tsx +24 -1
  383. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +4 -4
  384. package/src/components/data-table/schemas.ts +14 -36
  385. package/src/components/data-table/search-highlight.tsx +79 -0
  386. package/src/components/data-table/types.ts +1 -0
  387. package/src/components/data-table/url-detector.tsx +49 -9
  388. package/src/components/data-table/utils.ts +18 -0
  389. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  390. package/src/components/datasources/__tests__/column-preview.test.tsx +96 -5
  391. package/src/components/datasources/__tests__/datasources-interactions.test.tsx +181 -0
  392. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  393. package/src/components/datasources/__tests__/utils.test.ts +57 -6
  394. package/src/components/datasources/column-preview.tsx +17 -12
  395. package/src/components/datasources/datasources.tsx +56 -43
  396. package/src/components/datasources/utils.ts +32 -23
  397. package/src/components/debugger/debugger-code.tsx +5 -0
  398. package/src/components/dependency-graph/custom-node.tsx +1 -1
  399. package/src/components/dependency-graph/dependency-graph-tree.tsx +8 -8
  400. package/src/components/dependency-graph/elements.ts +24 -21
  401. package/src/components/dependency-graph/minimap-content.tsx +15 -20
  402. package/src/components/dependency-graph/panels.tsx +4 -2
  403. package/src/components/dependency-graph/utils/__tests__/layout.test.ts +18 -11
  404. package/src/components/editor/Output.tsx +9 -2
  405. package/src/components/editor/SortableCell.tsx +1 -3
  406. package/src/components/editor/__tests__/Output.test.tsx +50 -32
  407. package/src/components/editor/__tests__/agent-toast.test.tsx +58 -0
  408. package/src/components/editor/__tests__/data-attributes.test.tsx +28 -23
  409. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  410. package/src/components/editor/__tests__/output-persistence.test.tsx +28 -9
  411. package/src/components/editor/__tests__/package-alert.test.tsx +22 -1
  412. package/src/components/editor/actions/__tests__/name-cell-input.test.tsx +69 -0
  413. package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +69 -260
  414. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +222 -43
  415. package/src/components/editor/actions/__tests__/pair-with-agent.test.tsx +6 -24
  416. package/src/components/editor/actions/__tests__/useCellActionButton.test.tsx +131 -0
  417. package/src/components/editor/actions/name-cell-input.tsx +4 -3
  418. package/src/components/editor/actions/pair-with-agent-commands.ts +48 -54
  419. package/src/components/editor/actions/pair-with-agent-modal.tsx +102 -43
  420. package/src/components/editor/actions/pair-with-agent.tsx +16 -39
  421. package/src/components/editor/actions/useCellActionButton.tsx +47 -35
  422. package/src/components/editor/actions/useConfigActions.tsx +3 -4
  423. package/src/components/editor/actions/useNotebookActions.tsx +25 -29
  424. package/src/components/editor/actions/useRestartKernel.tsx +1 -1
  425. package/src/components/editor/actions/useSetCodeVisibility.ts +11 -12
  426. package/src/components/editor/actions/useSetOutputsExpanded.ts +14 -19
  427. package/src/components/editor/agent-toast.ts +15 -0
  428. package/src/components/editor/ai/ai-completion-editor.tsx +2 -1
  429. package/src/components/editor/ai/completion-handlers.tsx +0 -37
  430. package/src/components/editor/ai/prompt-input.tsx +189 -0
  431. package/src/components/editor/alerts/__tests__/connection-notice.test.tsx +1 -1
  432. package/src/components/editor/alerts/connection-notice.tsx +1 -1
  433. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  434. package/src/components/editor/alerts/stdin-blocking-alert.tsx +4 -2
  435. package/src/components/editor/app-container.tsx +1 -1
  436. package/src/components/editor/boundary/__tests__/ErrorBoundary.test.tsx +1 -1
  437. package/src/components/editor/cell/CellStatus.tsx +28 -23
  438. package/src/components/editor/cell/CreateCellButton.tsx +15 -21
  439. package/src/components/editor/cell/DeleteButton.tsx +3 -2
  440. package/src/components/editor/cell/RunButton.tsx +18 -8
  441. package/src/components/editor/cell/StagedAICell.tsx +52 -137
  442. package/src/components/editor/cell/StopButton.tsx +2 -2
  443. package/src/components/editor/cell/__tests__/useRunCells.test.tsx +169 -0
  444. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  445. package/src/components/editor/cell/cell-actions.tsx +12 -1
  446. package/src/components/editor/cell/cell-context-menu.tsx +4 -1
  447. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  448. package/src/components/editor/cell/code/cell-editor.tsx +65 -169
  449. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  450. package/src/components/editor/cell/collapse.tsx +16 -6
  451. package/src/components/editor/cell/useAddCell.ts +4 -8
  452. package/src/components/editor/cell/useDeleteCell.tsx +10 -36
  453. package/src/components/editor/cell/useRunCells.ts +49 -8
  454. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  455. package/src/components/editor/cell/useSplitCell.tsx +10 -6
  456. package/src/components/editor/chrome/components/__tests__/feedback-button.test.tsx +1 -1
  457. package/src/components/editor/chrome/components/feedback-button.tsx +2 -8
  458. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +31 -2
  459. package/src/components/editor/chrome/panels/__tests__/runtime-details.test.tsx +1 -1
  460. package/src/components/editor/chrome/panels/__tests__/sandbox-recovery.test.tsx +1 -1
  461. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  462. package/src/components/editor/chrome/panels/dependency-graph-panel.tsx +1 -1
  463. package/src/components/editor/chrome/panels/error-panel.tsx +1 -0
  464. package/src/components/editor/chrome/panels/logs-panel.tsx +2 -1
  465. package/src/components/editor/chrome/panels/outline/__tests__/outline-keyboard.test.tsx +84 -0
  466. package/src/components/editor/chrome/panels/outline/floating-outline.tsx +39 -14
  467. package/src/components/editor/chrome/panels/outline/useActiveOutline.tsx +3 -2
  468. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  469. package/src/components/editor/chrome/panels/panel-header.tsx +13 -0
  470. package/src/components/editor/chrome/panels/secrets-panel.tsx +1 -0
  471. package/src/components/editor/chrome/panels/session-panel.tsx +1 -1
  472. package/src/components/editor/chrome/panels/snippets-panel.tsx +10 -12
  473. package/src/components/editor/chrome/panels/variable-panel.tsx +1 -5
  474. package/src/components/editor/chrome/types.ts +1 -1
  475. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +364 -0
  476. package/src/components/editor/chrome/wrapper/__tests__/document-sync-status.test.tsx +92 -0
  477. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +265 -37
  478. package/src/components/editor/chrome/wrapper/__tests__/useAskAiAboutCell.test.tsx +58 -0
  479. package/src/components/editor/chrome/wrapper/app-chrome.tsx +91 -74
  480. package/src/components/editor/chrome/wrapper/footer-item.tsx +32 -24
  481. package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +237 -0
  482. package/src/components/editor/chrome/wrapper/footer-items/backend-status.tsx +1 -1
  483. package/src/components/editor/chrome/wrapper/footer-items/document-sync-status.tsx +54 -0
  484. package/src/components/editor/chrome/wrapper/footer-items/lsp-status.tsx +1 -1
  485. package/src/components/editor/chrome/wrapper/footer-items/machine-stats.tsx +1 -1
  486. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  487. package/src/components/editor/chrome/wrapper/footer.tsx +4 -2
  488. package/src/components/editor/chrome/wrapper/minimap-state.ts +1 -1
  489. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +143 -72
  490. package/src/components/editor/chrome/wrapper/pending-ai-changes-badge.tsx +25 -0
  491. package/src/components/editor/chrome/wrapper/sidebar.tsx +8 -4
  492. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  493. package/src/components/editor/chrome/wrapper/useAskAiAboutCell.ts +36 -0
  494. package/src/components/editor/columns/__tests__/cell-column.test.tsx +1 -1
  495. package/src/components/editor/columns/cell-column.tsx +1 -1
  496. package/src/components/editor/columns/sortable-column.tsx +2 -2
  497. package/src/components/editor/connections/components.tsx +5 -10
  498. package/src/components/editor/controls/Controls.tsx +4 -4
  499. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  500. package/src/components/editor/controls/__tests__/keyboard-shortcuts.test.tsx +61 -0
  501. package/src/components/editor/controls/__tests__/notebook-menu-dropdown.test.tsx +10 -4
  502. package/src/components/editor/controls/command-palette.tsx +22 -10
  503. package/src/components/editor/controls/keyboard-shortcuts.tsx +14 -5
  504. package/src/components/editor/documentation.css +34 -11
  505. package/src/components/editor/dynamic-favicon.tsx +21 -40
  506. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +541 -0
  507. package/src/components/editor/errors/auto-fix.tsx +4 -8
  508. package/src/components/editor/errors/send-error-report-button.tsx +66 -0
  509. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  510. package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +47 -0
  511. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  512. package/src/components/editor/file-tree/file-explorer.tsx +48 -15
  513. package/src/components/editor/file-tree/file-name-input.tsx +1 -0
  514. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  515. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  516. package/src/components/editor/header/__tests__/filename-form.test.tsx +1 -4
  517. package/src/components/editor/header/__tests__/status.test.tsx +5 -3
  518. package/src/components/editor/header/app-header.tsx +1 -1
  519. package/src/components/editor/header/filename-form.tsx +1 -1
  520. package/src/components/editor/header/status.tsx +44 -22
  521. package/src/components/editor/links/__tests__/cell-link.test.tsx +148 -0
  522. package/src/components/editor/links/cell-link-list.tsx +1 -1
  523. package/src/components/editor/links/cell-link.tsx +21 -11
  524. package/src/components/editor/navigation/__tests__/clipboard.test.ts +60 -38
  525. package/src/components/editor/navigation/__tests__/navigation.test.ts +108 -85
  526. package/src/components/editor/navigation/__tests__/selection.test.ts +43 -21
  527. package/src/components/editor/navigation/clipboard.ts +17 -14
  528. package/src/components/editor/navigation/focus-utils.ts +23 -4
  529. package/src/components/editor/navigation/multi-cell-action-toolbar.tsx +17 -33
  530. package/src/components/editor/navigation/navigation.ts +74 -72
  531. package/src/components/editor/navigation/selection.ts +30 -29
  532. package/src/components/editor/notebook-banner.tsx +1 -0
  533. package/src/components/editor/notebook-cell.tsx +126 -152
  534. package/src/components/editor/output/MarimoErrorOutput.tsx +60 -7
  535. package/src/components/editor/output/MarimoTracebackOutput.tsx +54 -37
  536. package/src/components/editor/output/Outputs.css +8 -0
  537. package/src/components/editor/output/VideoOutput.tsx +6 -0
  538. package/src/components/editor/output/__tests__/VideoOutput.test.tsx +131 -0
  539. package/src/components/editor/output/__tests__/ansi-reduce.test.ts +18 -0
  540. package/src/components/editor/output/__tests__/traceback.test.tsx +27 -1
  541. package/src/components/editor/output/ansi-reduce.ts +36 -17
  542. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  543. package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +125 -69
  544. package/src/components/editor/package-alert.tsx +14 -7
  545. package/src/components/editor/renderers/__tests__/add-cell-buttons.test.tsx +18 -0
  546. package/src/components/editor/renderers/cell-array.tsx +32 -100
  547. package/src/components/editor/renderers/grid-layout/__tests__/grid-layout.test.tsx +75 -0
  548. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +28 -22
  549. package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +0 -4
  550. package/src/components/editor/renderers/slides-layout/slides-layout.tsx +1 -1
  551. package/src/components/editor/renderers/vertical-layout/__tests__/useFocusFirstEditor.test.ts +4 -4
  552. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  553. package/src/components/editor/renderers/vertical-layout/useFocusFirstEditor.ts +1 -1
  554. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +19 -37
  555. package/src/components/editor/stdin-blocking-alert.tsx +4 -2
  556. package/src/components/home/components.tsx +17 -31
  557. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  558. package/src/components/icons/large-spinner.tsx +3 -14
  559. package/src/components/icons/spinner.tsx +1 -1
  560. package/src/components/markdown/__tests__/markdown-renderer.test.tsx +32 -2
  561. package/src/components/markdown/markdown-renderer.tsx +43 -28
  562. package/src/components/mcp/mcp-status-indicator.tsx +18 -14
  563. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  564. package/src/components/pages/edit-page.tsx +19 -17
  565. package/src/components/pages/home-page.tsx +164 -91
  566. package/src/components/scratchpad/scratchpad.test.tsx +89 -0
  567. package/src/components/scratchpad/scratchpad.tsx +47 -33
  568. package/src/components/scratchpad/state.ts +7 -0
  569. package/src/components/slides/__tests__/minimap-actions.test.tsx +22 -29
  570. package/src/components/slides/__tests__/minimap.test.ts +38 -19
  571. package/src/components/slides/__tests__/slide-scroll-hint.test.tsx +37 -0
  572. package/src/components/slides/minimap.tsx +22 -14
  573. package/src/components/slides/reveal-component.tsx +73 -36
  574. package/src/components/slides/reveal-slides.css +5 -4
  575. package/src/components/slides/slide-cell-view.tsx +37 -34
  576. package/src/components/slides/slide-form.tsx +1 -0
  577. package/src/components/slides/slide-scroll-hint.tsx +3 -1
  578. package/src/components/slides/slide.tsx +3 -4
  579. package/src/components/sort/SortableCellsProvider.tsx +8 -25
  580. package/src/components/tracing/tracing-keyboard.test.tsx +85 -0
  581. package/src/components/tracing/tracing.tsx +46 -42
  582. package/src/components/ui/__tests__/draggable-popover.test.tsx +24 -0
  583. package/src/components/ui/__tests__/fullscreen.test.tsx +44 -2
  584. package/src/components/ui/__tests__/range-slider.test.tsx +165 -0
  585. package/src/components/ui/alert-dialog.tsx +1 -1
  586. package/src/components/ui/draggable-popover.tsx +24 -2
  587. package/src/components/ui/fullscreen.tsx +25 -1
  588. package/src/components/ui/range-slider.tsx +22 -9
  589. package/src/components/ui/toast.tsx +1 -1
  590. package/src/core/MarimoApp.tsx +3 -1
  591. package/src/core/ai/__tests__/staged-cells.test.ts +89 -608
  592. package/src/core/ai/context/context.ts +2 -0
  593. package/src/core/ai/context/providers/__tests__/cell-output.test.ts +8 -12
  594. package/src/core/ai/context/providers/__tests__/cell.test.ts +49 -0
  595. package/src/core/ai/context/providers/cell-output.ts +2 -2
  596. package/src/core/ai/context/providers/cell.ts +54 -0
  597. package/src/core/ai/context/providers/datasource.ts +6 -2
  598. package/src/core/ai/staged-cells.ts +56 -323
  599. package/src/core/ai/state.ts +3 -0
  600. package/src/core/ai/tools/__tests__/edit-notebook-tool.test.ts +40 -88
  601. package/src/core/ai/tools/__tests__/registry.test.ts +17 -0
  602. package/src/core/ai/tools/base.ts +18 -15
  603. package/src/core/ai/tools/edit-notebook-tool.ts +86 -80
  604. package/src/core/ai/tools/registry.ts +13 -10
  605. package/src/core/ai/tools/sample-tool.ts +6 -7
  606. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -529
  607. package/src/core/cells/__tests__/actions.test.ts +623 -35
  608. package/src/core/cells/__tests__/add-missing-import.test.ts +7 -8
  609. package/src/core/cells/__tests__/arrange.test.ts +432 -0
  610. package/src/core/cells/__tests__/cell.test.ts +148 -150
  611. package/src/core/cells/__tests__/cells.test.ts +408 -2891
  612. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
  613. package/src/core/cells/__tests__/document.test.ts +158 -0
  614. package/src/core/cells/__tests__/editor.test.ts +57 -0
  615. package/src/core/cells/__tests__/error-report.test.ts +139 -0
  616. package/src/core/cells/__tests__/flush-document.test.ts +40 -0
  617. package/src/core/cells/__tests__/harness.ts +106 -0
  618. package/src/core/cells/__tests__/layout.test.ts +176 -0
  619. package/src/core/cells/__tests__/logs.test.ts +0 -13
  620. package/src/core/cells/__tests__/notebook-outline.test.ts +122 -0
  621. package/src/core/cells/__tests__/outputs.test.tsx +71 -53
  622. package/src/core/cells/__tests__/pending-delete-service.test.tsx +11 -20
  623. package/src/core/cells/__tests__/session.test.ts +594 -210
  624. package/src/core/cells/__tests__/utils.test.ts +135 -337
  625. package/src/core/cells/actions.ts +677 -6
  626. package/src/core/cells/add-missing-import.ts +47 -43
  627. package/src/core/cells/arrange.ts +529 -0
  628. package/src/core/cells/cell.ts +78 -146
  629. package/src/core/cells/cells.ts +109 -1460
  630. package/src/core/cells/columns.ts +38 -0
  631. package/src/core/cells/document.ts +167 -0
  632. package/src/core/cells/edit.ts +30 -0
  633. package/src/core/cells/editor.ts +82 -0
  634. package/src/core/cells/error-report.ts +63 -0
  635. package/src/core/cells/focus.ts +27 -6
  636. package/src/core/cells/layout.ts +221 -0
  637. package/src/core/cells/outputs.ts +12 -28
  638. package/src/core/cells/pending-delete-service.ts +8 -14
  639. package/src/core/cells/scrollCellIntoView.ts +6 -0
  640. package/src/core/cells/session.ts +184 -59
  641. package/src/core/cells/types.ts +21 -51
  642. package/src/core/cells/utils.ts +85 -87
  643. package/src/core/codemirror/__tests__/format.test.ts +0 -34
  644. package/src/core/codemirror/__tests__/setup.test.ts +61 -29
  645. package/src/core/codemirror/ai/__tests__/resources.test.ts +117 -0
  646. package/src/core/codemirror/ai/resources.ts +1 -1
  647. package/src/core/codemirror/cells/__tests__/extensions.test.ts +2 -6
  648. package/src/core/codemirror/cells/extensions.ts +18 -30
  649. package/src/core/codemirror/cells/state.ts +2 -6
  650. package/src/core/codemirror/cm.ts +5 -1
  651. package/src/core/codemirror/config/types.ts +1 -1
  652. package/src/core/codemirror/copilot/__tests__/getCodes.test.ts +6 -7
  653. package/src/core/codemirror/copilot/getCodes.ts +2 -2
  654. package/src/core/codemirror/format.ts +6 -23
  655. package/src/core/codemirror/keymaps/vim.ts +4 -8
  656. package/src/core/codemirror/language/__tests__/sql.test.ts +22 -20
  657. package/src/core/codemirror/language/__tests__/utils.test.ts +187 -15
  658. package/src/core/codemirror/language/cell-code.ts +114 -0
  659. package/src/core/codemirror/language/extension.ts +52 -35
  660. package/src/core/codemirror/language/languages/markdown.ts +8 -6
  661. package/src/core/codemirror/language/languages/python.ts +19 -13
  662. package/src/core/codemirror/language/languages/sql/sql.ts +16 -24
  663. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  664. package/src/core/codemirror/language/types.ts +7 -7
  665. package/src/core/codemirror/language/utils.ts +9 -39
  666. package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +1 -2
  667. package/src/core/codemirror/lsp/notebook-lsp.ts +6 -5
  668. package/src/core/codemirror/lsp/utils.ts +1 -1
  669. package/src/core/codemirror/misc/__tests__/paste.test.ts +3 -1
  670. package/src/core/codemirror/misc/paste.ts +1 -1
  671. package/src/core/codemirror/placeholder/__tests__/extensions.test.ts +75 -0
  672. package/src/core/codemirror/placeholder/extensions.ts +80 -19
  673. package/src/core/codemirror/readonly/__tests__/extension.test.ts +1 -1
  674. package/src/core/codemirror/readonly/extension.ts +2 -2
  675. package/src/core/codemirror/utils.ts +1 -0
  676. package/src/core/config/__tests__/app-config.test.ts +64 -0
  677. package/src/core/config/__tests__/config-schema.test.ts +56 -6
  678. package/src/core/config/config-schema.ts +17 -4
  679. package/src/core/config/config.ts +65 -20
  680. package/src/core/config/feature-flag.tsx +0 -2
  681. package/src/core/config/pair.ts +5 -3
  682. package/src/core/datasets/__tests__/data-source-discovery.test.ts +5 -5
  683. package/src/core/datasets/data-source-discovery.ts +4 -6
  684. package/src/core/documentation/doc-lookup.ts +1 -1
  685. package/src/core/dom/outline.ts +4 -1
  686. package/src/core/edit-app.tsx +22 -28
  687. package/src/core/errors/__tests__/error-entries.test.ts +40 -1
  688. package/src/core/errors/error-entries.ts +6 -4
  689. package/src/core/executions/__tests__/atoms.test.ts +98 -0
  690. package/src/core/executions/__tests__/freshness.test.ts +482 -0
  691. package/src/core/executions/__tests__/hooks.test.tsx +151 -0
  692. package/src/core/executions/__tests__/output.test.ts +266 -0
  693. package/src/core/executions/__tests__/select.test.ts +268 -0
  694. package/src/core/executions/atoms.ts +263 -0
  695. package/src/core/executions/freshness.ts +171 -0
  696. package/src/core/executions/hooks.ts +86 -0
  697. package/src/core/executions/index.ts +48 -0
  698. package/src/core/executions/output.ts +242 -0
  699. package/src/core/executions/select.ts +175 -0
  700. package/src/core/export/__tests__/hooks.test.ts +0 -5
  701. package/src/core/export/hooks.ts +1 -1
  702. package/src/core/export/layout.ts +2 -2
  703. package/src/core/hotkeys/actions.ts +2 -4
  704. package/src/core/hotkeys/hotkeys.ts +2 -1
  705. package/src/core/islands/__tests__/bootstrap.test.ts +69 -17
  706. package/src/core/islands/__tests__/bridge.test.ts +85 -27
  707. package/src/core/islands/bootstrap.ts +49 -54
  708. package/src/core/islands/bridge.ts +84 -25
  709. package/src/core/islands/components/__tests__/IslandControls.test.tsx +7 -5
  710. package/src/core/islands/components/__tests__/web-components.test.tsx +63 -0
  711. package/src/core/islands/components/output-wrapper.tsx +5 -5
  712. package/src/core/islands/components/web-components.tsx +12 -1
  713. package/src/core/islands/worker/__tests__/controller.test.ts +62 -4
  714. package/src/core/islands/worker/controller.ts +4 -10
  715. package/src/core/islands/worker/worker.tsx +33 -33
  716. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  717. package/src/core/kernel/__tests__/handlers.test.ts +7 -157
  718. package/src/core/kernel/handlers.ts +13 -79
  719. package/src/core/kernel/state.ts +0 -15
  720. package/src/core/lifecycle/render-policy.ts +12 -26
  721. package/src/core/meta/__tests__/code-visibility.test.tsx +78 -70
  722. package/src/core/meta/code-visibility.ts +18 -9
  723. package/src/core/network/__tests__/connection.test.ts +1 -1
  724. package/src/core/network/__tests__/requests-lazy.test.ts +6 -8
  725. package/src/core/network/connection-notice.ts +1 -1
  726. package/src/core/network/connection.ts +5 -3
  727. package/src/core/network/requests-lazy.ts +5 -8
  728. package/src/core/network/requests-network.ts +5 -31
  729. package/src/core/network/requests-static.ts +1 -4
  730. package/src/core/network/requests-toasting.tsx +1 -4
  731. package/src/core/network/types.ts +6 -14
  732. package/src/core/network/useConnectionNotice.ts +8 -2
  733. package/src/core/notebook/__tests__/operations.test.ts +51 -0
  734. package/src/core/notebook/attachments.ts +38 -0
  735. package/src/core/notebook/document/__tests__/codemirror.test.ts +759 -0
  736. package/src/core/notebook/document/__tests__/draft.test.ts +363 -0
  737. package/src/core/notebook/document/__tests__/fixtures.ts +92 -0
  738. package/src/core/notebook/document/__tests__/hooks.test.tsx +391 -0
  739. package/src/core/notebook/document/__tests__/lifecycle-scenarios.test.ts +61 -0
  740. package/src/core/notebook/document/__tests__/model.test.ts +247 -0
  741. package/src/core/notebook/document/__tests__/protocol-fixtures.ts +38 -0
  742. package/src/core/notebook/document/__tests__/recovery-scenarios.test.ts +230 -0
  743. package/src/core/notebook/document/__tests__/scenario-fixture.ts +43 -0
  744. package/src/core/notebook/document/__tests__/scenarios.test.ts +205 -0
  745. package/src/core/notebook/document/__tests__/state.test.ts +459 -0
  746. package/src/core/notebook/document/__tests__/submission.test.ts +203 -0
  747. package/src/core/notebook/document/__tests__/test-host.test.ts +92 -0
  748. package/src/core/notebook/document/__tests__/test-host.ts +247 -0
  749. package/src/core/notebook/document/__tests__/test-notebook.ts +149 -0
  750. package/src/core/notebook/document/__tests__/user.ts +61 -0
  751. package/src/core/notebook/document/codemirror.ts +216 -0
  752. package/src/core/notebook/document/draft.ts +502 -0
  753. package/src/core/notebook/document/hooks.tsx +118 -0
  754. package/src/core/notebook/document/index.ts +24 -0
  755. package/src/core/notebook/document/model.ts +102 -0
  756. package/src/core/notebook/document/select.ts +41 -0
  757. package/src/core/notebook/document/state.ts +378 -0
  758. package/src/core/notebook/document/sync.ts +369 -0
  759. package/src/core/notebook/index.ts +19 -0
  760. package/src/core/notebook/operations.ts +30 -0
  761. package/src/core/notebook/replica/AGENTS.md +11 -0
  762. package/src/core/notebook/replica/__tests__/attachments.test.ts +56 -0
  763. package/src/core/notebook/replica/__tests__/document.test.ts +126 -0
  764. package/src/core/notebook/replica/__tests__/executions.test.ts +200 -0
  765. package/src/core/notebook/replica/__tests__/fixtures.ts +79 -0
  766. package/src/core/notebook/replica/__tests__/replica.test.ts +559 -0
  767. package/src/core/notebook/replica/__tests__/results.test.ts +193 -0
  768. package/src/core/notebook/replica/__tests__/runtime.test.ts +78 -0
  769. package/src/core/notebook/replica/backend.ts +19 -0
  770. package/src/core/{websocket/types.ts → notebook/replica/connection.ts} +10 -15
  771. package/src/core/notebook/replica/index.ts +20 -0
  772. package/src/core/notebook/replica/replica.ts +336 -0
  773. package/src/core/notebook/replica/resources/attachments.ts +22 -0
  774. package/src/core/notebook/replica/resources/document.ts +101 -0
  775. package/src/core/notebook/replica/resources/executions.ts +157 -0
  776. package/src/core/notebook/replica/resources/results.ts +48 -0
  777. package/src/core/notebook/replica/resources/runtime.ts +14 -0
  778. package/src/core/packages/__tests__/useSandboxController.test.tsx +1 -1
  779. package/src/core/packages/useSandboxController.ts +1 -1
  780. package/src/core/run-app.tsx +49 -51
  781. package/src/core/runtime/__tests__/adapter.test.ts +4 -4
  782. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  783. package/src/core/runtime/__tests__/runtime.test.ts +33 -31
  784. package/src/core/runtime/adapter.ts +5 -5
  785. package/src/core/runtime/config.ts +2 -2
  786. package/src/core/runtime/runtime.ts +33 -32
  787. package/src/core/saving/__tests__/document.test.tsx +123 -0
  788. package/src/core/saving/__tests__/state.test.ts +111 -0
  789. package/src/core/saving/__tests__/useAutoSave.test.tsx +67 -0
  790. package/src/core/saving/document.ts +33 -0
  791. package/src/core/saving/filename.ts +1 -1
  792. package/src/core/saving/save-component.tsx +39 -60
  793. package/src/core/saving/state.ts +38 -39
  794. package/src/core/saving/useAutoSave.ts +17 -53
  795. package/src/core/session/__tests__/backend.test.ts +236 -0
  796. package/src/core/session/__tests__/useNotebookReplica.hook.test.tsx +186 -0
  797. package/src/core/{websocket/__tests__/useMarimoKernelConnection.hook.test.tsx → session/__tests__/useNotebookSession.hook.test.tsx} +282 -183
  798. package/src/core/session/active-notebook.ts +28 -0
  799. package/src/core/session/backend.ts +123 -0
  800. package/src/core/session/embedded.ts +7 -0
  801. package/src/core/session/types.ts +17 -0
  802. package/src/core/session/useNotebookReplica.ts +74 -0
  803. package/src/core/{websocket/useMarimoKernelConnection.tsx → session/useNotebookSession.tsx} +123 -276
  804. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  805. package/src/core/state/jotai.ts +1 -1
  806. package/src/core/static/__tests__/files.test.ts +38 -10
  807. package/src/core/static/files.ts +2 -0
  808. package/src/core/{websocket/__tests__/useMarimoKernelConnection.test.ts → transports/__tests__/connection-state.test.ts} +16 -30
  809. package/src/core/transports/__tests__/server.test.ts +408 -0
  810. package/src/core/transports/__tests__/static.test.ts +340 -0
  811. package/src/core/transports/__tests__/wasm.test.ts +166 -0
  812. package/src/core/transports/connection-state.ts +63 -0
  813. package/src/core/transports/server.ts +152 -0
  814. package/src/core/transports/static.ts +173 -0
  815. package/src/core/transports/wasm.ts +97 -0
  816. package/src/core/vscode/__tests__/vscode-bindings.test.ts +125 -0
  817. package/src/core/vscode/vscode-bindings.ts +14 -2
  818. package/src/core/wasm/__tests__/bridge.test.ts +210 -64
  819. package/src/core/wasm/__tests__/worker.test.ts +188 -0
  820. package/src/core/wasm/bridge.ts +120 -73
  821. package/src/core/wasm/rpc.ts +15 -3
  822. package/src/core/wasm/store.ts +3 -3
  823. package/src/core/wasm/worker/bootstrap.ts +55 -51
  824. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  825. package/src/core/wasm/worker/save-worker.ts +9 -10
  826. package/src/core/wasm/worker/types.ts +30 -18
  827. package/src/core/wasm/worker/worker.ts +118 -72
  828. package/src/css/app/Cell.css +7 -36
  829. package/src/css/app/codemirror.css +1 -1
  830. package/src/css/common.css +1 -1
  831. package/src/css/globals.css +72 -24
  832. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  833. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  834. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  835. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  836. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  837. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  838. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  839. package/src/hooks/useAsyncData.ts +3 -3
  840. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  841. package/src/hooks/useDebounce.ts +6 -10
  842. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  843. package/src/hooks/useDelayElapsed.ts +14 -10
  844. package/src/hooks/useElapsedTime.ts +3 -3
  845. package/src/hooks/useEventListener.ts +1 -2
  846. package/src/hooks/useInputHistory.ts +4 -7
  847. package/src/hooks/useInternalStateWithSync.ts +6 -6
  848. package/src/hooks/useResizeObserver.ts +4 -7
  849. package/src/hooks/useScript.ts +67 -70
  850. package/src/hooks/useStateForKey.ts +93 -0
  851. package/src/mount.tsx +23 -14
  852. package/src/plugins/__tests__/plugin-object-schemas.test.ts +104 -0
  853. package/src/plugins/core/RenderHTML.tsx +3 -5
  854. package/src/plugins/core/__test__/PluginSlot.test.tsx +202 -0
  855. package/src/plugins/core/__test__/RenderHTML-copy.test.tsx +60 -0
  856. package/src/plugins/core/__test__/registerReactComponent.test.ts +45 -0
  857. package/src/plugins/core/__test__/sanitize.test.ts +8 -0
  858. package/src/plugins/core/registerReactComponent.tsx +29 -29
  859. package/src/plugins/core/sanitize-html.ts +6 -0
  860. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  861. package/src/plugins/impl/DataEditorPlugin.tsx +34 -13
  862. package/src/plugins/impl/DataTablePlugin.tsx +69 -37
  863. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  864. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  865. package/src/plugins/impl/FormPlugin.tsx +1 -1
  866. package/src/plugins/impl/RangeSliderPlugin.tsx +16 -2
  867. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  868. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  869. package/src/plugins/impl/__tests__/DataEditorPlugin.test.ts +93 -4
  870. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +471 -1
  871. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  872. package/src/plugins/impl/__tests__/RangeSliderPlugin.test.tsx +109 -0
  873. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  874. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +153 -0
  875. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  876. package/src/plugins/impl/anywidget/model.ts +3 -1
  877. package/src/plugins/impl/anywidget/registry.ts +18 -14
  878. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  879. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  880. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  881. package/src/plugins/impl/chat/chat-ui.tsx +17 -15
  882. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  883. package/src/plugins/impl/common/error-banner.tsx +5 -3
  884. package/src/plugins/impl/data-editor/__tests__/column-types.test.ts +42 -0
  885. package/src/plugins/impl/data-editor/__tests__/editor-state.test.ts +52 -1
  886. package/src/plugins/impl/data-editor/__tests__/glide-data-editor.test.tsx +120 -4
  887. package/src/plugins/impl/data-editor/__tests__/glide-utils.test.ts +116 -0
  888. package/src/plugins/impl/data-editor/column-types.ts +55 -0
  889. package/src/plugins/impl/data-editor/editor-state.ts +18 -2
  890. package/src/plugins/impl/data-editor/glide-data-editor.tsx +74 -12
  891. package/src/plugins/impl/data-editor/glide-utils.ts +39 -38
  892. package/src/plugins/impl/data-editor/types.ts +5 -0
  893. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  894. package/src/plugins/impl/data-explorer/components/column-summary.tsx +4 -2
  895. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  896. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -2
  897. package/src/plugins/impl/data-frames/__tests__/sidebar.test.tsx +160 -0
  898. package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +66 -66
  899. package/src/plugins/impl/data-frames/panel.tsx +44 -16
  900. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  901. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  902. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  903. package/src/plugins/impl/panel/utils.ts +17 -0
  904. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +2 -5
  905. package/src/plugins/impl/plotly/selection.ts +25 -19
  906. package/src/plugins/impl/vega/VegaPlugin.tsx +2 -3
  907. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  908. package/src/plugins/layout/DownloadPlugin.tsx +7 -11
  909. package/src/plugins/layout/LazyPlugin.tsx +7 -4
  910. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  911. package/src/plugins/layout/__test__/DownloadPlugin.test.tsx +205 -0
  912. package/src/plugins/layout/__tests__/LazyPlugin.test.tsx +98 -0
  913. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  914. package/src/stories/button.stories.tsx +1 -0
  915. package/src/stories/cell-status.stories.tsx +29 -24
  916. package/src/stories/cell.stories.tsx +46 -46
  917. package/src/stories/data-table.stories.tsx +31 -0
  918. package/src/stories/editor.stories.tsx +24 -17
  919. package/src/stories/form-wrapper.stories.tsx +2 -2
  920. package/src/stories/layout/vertical/one-column.stories.tsx +28 -42
  921. package/src/stories/runtime-story.tsx +23 -17
  922. package/src/stories/select.stories.tsx +2 -2
  923. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  924. package/src/theme/__tests__/islands-theme.test.tsx +276 -0
  925. package/src/theme/namespace.tsx +4 -3
  926. package/src/theme/useTheme.ts +71 -41
  927. package/src/utils/__tests__/arrays.test.ts +35 -0
  928. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  929. package/src/utils/__tests__/{createReducer.test.ts → createReducer.test.tsx} +127 -1
  930. package/src/utils/__tests__/dates.test.ts +116 -0
  931. package/src/utils/__tests__/download.test.tsx +183 -23
  932. package/src/utils/__tests__/numbers.test.ts +105 -1
  933. package/src/utils/__tests__/objects.test.ts +21 -0
  934. package/src/utils/arrays.ts +11 -2
  935. package/src/utils/blob.ts +1 -1
  936. package/src/utils/createReducer.ts +115 -79
  937. package/src/utils/dates.ts +94 -0
  938. package/src/utils/download.ts +67 -6
  939. package/src/utils/maps.ts +0 -3
  940. package/src/utils/numbers.ts +79 -18
  941. package/src/utils/objects.ts +4 -9
  942. package/src/utils/once.ts +19 -19
  943. package/src/utils/time.ts +10 -0
  944. package/src/utils/tracer.ts +9 -5
  945. package/src/utils/zod-utils.ts +1 -3
  946. package/dist/_baseMerge-BiGRwuqv.js +0 -165
  947. package/dist/any-language-editor-B9REGlD9.js +0 -76
  948. package/dist/architecture-7GRP2DOG-BWLylgiA.js +0 -3
  949. package/dist/assets/__vite-browser-external-BXYkiFsk.js +0 -1
  950. package/dist/assets/worker-HPY9fbhz.js +0 -114
  951. package/dist/classDiagram-ZZMXUADV-Bt98lkkv.js +0 -30
  952. package/dist/classDiagram-v2-VYDZK3BY-D9bfbcC3.js +0 -30
  953. package/dist/code-visibility-BCpXa0Xh.js +0 -138
  954. package/dist/cynefin-OW5HDTMX-obq-i80l.js +0 -3
  955. package/dist/error-entries-CzoYkIQZ.js +0 -56
  956. package/dist/eventmodeling-NTZA5JFV-D7nP7Uyu.js +0 -3
  957. package/dist/flowDiagram-HODETNUW-CAYbpJmV.js +0 -16
  958. package/dist/gitGraph-4MIJSDKK-Dy9uQXGh.js +0 -3
  959. package/dist/hasIn-o2Us0uVx.js +0 -132
  960. package/dist/html-to-image-CZY1pIZH.js +0 -3251
  961. package/dist/info-A6RAGUB7-DKoayiHY.js +0 -3
  962. package/dist/isArrayLikeObject-BZl-15ir.js +0 -450
  963. package/dist/isEmpty-BSZ1LaUo.js +0 -35
  964. package/dist/isSymbol-kQcfYbNX.js +0 -8
  965. package/dist/label-BDKF3Afc.js +0 -281
  966. package/dist/linear-Cnaq3Tfb.js +0 -204
  967. package/dist/mermaid-HWGCJPDP-ByCsZnYF.js +0 -2
  968. package/dist/packet-AYTQ26CC-Yw7ajchJ.js +0 -3
  969. package/dist/pick-CJeoxYOS.js +0 -51
  970. package/dist/pie-WAS4IAKB-BKdKOAkL.js +0 -3
  971. package/dist/precisionRound-DMIVQrt2.js +0 -97
  972. package/dist/purify.es-DMv5zIkJ.js +0 -866
  973. package/dist/radar-RG4KPBEZ-ScR_D_Ts.js +0 -3
  974. package/dist/railroad-74A4TZTK-DQvPSdo5.js +0 -3
  975. package/dist/railroad-abnf-HS5TGJTU-CsJQG2O8.js +0 -3
  976. package/dist/railroad-ebnf-LZEXJU2U-BgEobkSn.js +0 -3
  977. package/dist/railroad-peg-WCYAUIDC-DjLAjofI.js +0 -3
  978. package/dist/select-BButFi-d.js +0 -1344
  979. package/dist/stateDiagram-v2-MP3YSRHH-CISYxma6.js +0 -30
  980. package/dist/swimlanesDiagram-VR7AAH4N-c8G5pGxC.js +0 -30
  981. package/dist/toast-B9UMEi8u.js +0 -1830
  982. package/dist/treeView-Q6P3EWNA-DuRV97YF.js +0 -3
  983. package/dist/treemap-WGGIJYW6-AsLq-uwC.js +0 -3
  984. package/dist/use-restore-focus-BEM7sFWl.js +0 -6369
  985. package/dist/useLifecycle-j7D9uD1o.js +0 -169
  986. package/dist/useTheme-CRnjkku0.js +0 -904
  987. package/dist/wardley-WFR3VGLG-Na-tFhQJ.js +0 -3
  988. package/src/components/chat/reasoning-accordion.tsx +0 -61
  989. package/src/components/chat/tool-call/tool-error-card.tsx +0 -81
  990. package/src/components/chat/tool-call/tool-history-row.tsx +0 -174
  991. package/src/components/editor/ai/__tests__/staged-cell-submission.test.ts +0 -106
  992. package/src/components/editor/ai/add-cell-with-ai.tsx +0 -506
  993. package/src/components/editor/ai/staged-cell-submission.ts +0 -46
  994. package/src/components/editor/cell/__tests__/StagedAICell.test.tsx +0 -64
  995. package/src/components/editor/cell/code/__tests__/rtc-editor-config.test.ts +0 -50
  996. package/src/components/editor/chrome/wrapper/footer-items/rtc-status.tsx +0 -65
  997. package/src/components/editor/navigation/__tests__/state.test.ts +0 -81
  998. package/src/components/editor/navigation/state.ts +0 -65
  999. package/src/components/icons/loading-ellipsis.tsx +0 -46
  1000. package/src/core/ai/tools/__tests__/run-cells-tool.test.ts +0 -656
  1001. package/src/core/ai/tools/run-cells-tool.ts +0 -303
  1002. package/src/core/cells/__tests__/apply-transaction.test.ts +0 -991
  1003. package/src/core/cells/__tests__/document-changes.test.ts +0 -1140
  1004. package/src/core/cells/__tests__/document-roundtrip.test.ts +0 -392
  1005. package/src/core/cells/document-changes.ts +0 -944
  1006. package/src/core/codemirror/rtc/extension.ts +0 -453
  1007. package/src/core/codemirror/rtc/loro/__tests__/sync.test.ts +0 -52
  1008. package/src/core/codemirror/rtc/loro/awareness.ts +0 -531
  1009. package/src/core/codemirror/rtc/loro/colors.ts +0 -38
  1010. package/src/core/codemirror/rtc/loro/sync.ts +0 -168
  1011. package/src/core/host/AGENTS.md +0 -6
  1012. package/src/core/host/__tests__/protocol.test.ts +0 -214
  1013. package/src/core/host/protocol.ts +0 -1943
  1014. package/src/core/rtc/state.ts +0 -34
  1015. package/src/core/state/__tests__/jotai.test.ts +0 -25
  1016. package/src/core/wasm/worker/message-buffer.ts +0 -33
  1017. package/src/core/websocket/__tests__/useWebSocket.test.ts +0 -49
  1018. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  1019. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -338
  1020. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  1021. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  1022. package/src/core/websocket/transports/basic.ts +0 -102
  1023. package/src/core/websocket/transports/sse.ts +0 -308
  1024. package/src/core/websocket/transports/transport.ts +0 -61
  1025. package/src/core/websocket/transports/ws.ts +0 -98
  1026. package/src/core/websocket/useWebSocket.tsx +0 -88
  1027. package/src/utils/__tests__/id-tree.test.ts +0 -2145
  1028. package/src/utils/id-tree.tsx +0 -1134
  1029. /package/src/core/{websocket → session}/connection-utils.ts +0 -0
@@ -4,29 +4,32 @@ import { python } from "@codemirror/lang-python";
4
4
  import { EditorState } from "@codemirror/state";
5
5
  import { EditorView } from "@codemirror/view";
6
6
  import { createStore } from "jotai";
7
+ import { createRef } from "react";
7
8
  import {
8
9
  afterAll,
9
- afterEach,
10
10
  beforeAll,
11
+ afterEach,
11
12
  beforeEach,
12
13
  describe,
13
14
  expect,
14
15
  it,
15
16
  vi,
16
17
  } from "vitest";
18
+ import { MockExecutions } from "@/__mocks__/executions";
19
+ import {
20
+ notebookQueuedOrRunningCountAtom,
21
+ submitCells,
22
+ } from "@/core/executions";
23
+ import { store } from "@/core/state/jotai";
17
24
  import { MockNotebook } from "@/__mocks__/notebook";
18
25
  import { cellId } from "@/__tests__/branded";
19
26
  import type { CellHandle } from "@/components/editor/notebook-cell";
20
- import { CellId, SCRATCH_CELL_ID, SETUP_CELL_ID } from "@/core/cells/ids";
27
+ import { CellId, SCRATCH_CELL_ID } from "@/core/cells/ids";
21
28
  import { foldAllBulk, unfoldAllBulk } from "@/core/codemirror/editing/commands";
22
29
  import { adaptiveLanguageConfiguration } from "@/core/codemirror/language/extension";
23
30
  import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
24
31
  import type { OutputMessage } from "@/core/kernel/messages";
25
- import {
26
- type CollapsibleTree,
27
- MultiColumn,
28
- type CellColumnId,
29
- } from "@/utils/id-tree";
32
+ import type { CellConfig } from "@/core/network/types";
30
33
  import type { Seconds } from "@/utils/time";
31
34
  import {
32
35
  exportedForTesting,
@@ -34,13 +37,12 @@ import {
34
37
  type NotebookState,
35
38
  notebookAtom,
36
39
  } from "../cells";
37
- import { exportedForTesting as documentTransactionTestExports } from "../document-changes";
38
40
  import {
39
- focusAndScrollCellIntoView,
40
- scrollToBottom,
41
- scrollToTop,
42
- } from "../scrollCellIntoView";
43
- import type { CellData } from "../types";
41
+ type CellData,
42
+ createCell,
43
+ createCellConfig,
44
+ createCellRuntimeState,
45
+ } from "../types";
44
46
 
45
47
  vi.mock("@/core/codemirror/editing/commands", () => ({
46
48
  foldAllBulk: vi.fn(),
@@ -60,9 +62,6 @@ vi.mock("../scrollCellIntoView", async (importOriginal) => {
60
62
  };
61
63
  });
62
64
 
63
- const FIRST_COLUMN = 0;
64
- const SECOND_COLUMN = 1;
65
-
66
65
  const { initialNotebookState, reducer, createActions } = exportedForTesting;
67
66
 
68
67
  function formatCells(notebook: NotebookState) {
@@ -78,14 +77,7 @@ function formatCells(notebook: NotebookState) {
78
77
  const cells = cellsId.map((cellId) => cellData[cellId]);
79
78
  return cells.map((cell) => printCell(cell)).join("\n\n");
80
79
  };
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));
80
+ return wrap(printCells(cellIds));
89
81
  }
90
82
 
91
83
  function createEditor(content: string) {
@@ -121,8 +113,7 @@ describe("cell reducer", () => {
121
113
  let cells: ReturnType<typeof flattenTopLevelNotebookCells>;
122
114
  let firstCellId: CellId;
123
115
 
124
- const actions = createActions((action) => {
125
- state = reducer(state, action);
116
+ const mountEditors = () => {
126
117
  for (const [cellIdString, handle] of Object.entries(state.cellHandles)) {
127
118
  // @ts-expect-error - Typescript Object.entries doesn't know that keys are CellId
128
119
  const cid: CellId = cellIdString;
@@ -136,7 +127,83 @@ describe("cell reducer", () => {
136
127
  }
137
128
  }
138
129
  cells = flattenTopLevelNotebookCells(state);
139
- });
130
+ };
131
+ const actions = createActions((action) => {
132
+ state = reducer(state, action);
133
+ mountEditors();
134
+ });
135
+
136
+ /** Put a cell into the notebook the way the document would. */
137
+ function seedCell({
138
+ cellId,
139
+ before,
140
+ code = "",
141
+ name,
142
+ config,
143
+ newCellId,
144
+ hideCode,
145
+ autoFocus = true,
146
+ }: {
147
+ cellId: CellId | "__end__";
148
+ before: boolean;
149
+ code?: string;
150
+ name?: string;
151
+ config?: CellConfig;
152
+ newCellId?: CellId;
153
+ hideCode?: boolean;
154
+ autoFocus?: boolean;
155
+ }): CellId {
156
+ const cellIndex =
157
+ cellId === "__end__"
158
+ ? state.cellIds.length
159
+ : state.cellIds.indexOf(cellId);
160
+ const id = newCellId ?? CellId.create();
161
+ const cellIds = [...state.cellIds];
162
+ cellIds.splice(before ? cellIndex : cellIndex + 1, 0, id);
163
+ state = {
164
+ ...state,
165
+ cellIds,
166
+ cellData: {
167
+ ...state.cellData,
168
+ [id]: createCell({
169
+ id,
170
+ code,
171
+ name,
172
+ config: createCellConfig({
173
+ ...config,
174
+ ...(hideCode != null && { hide_code: hideCode }),
175
+ }),
176
+ }),
177
+ },
178
+ cellRuntime: { ...state.cellRuntime, [id]: createCellRuntimeState() },
179
+ cellHandles: { ...state.cellHandles, [id]: createRef() },
180
+ scrollKey: autoFocus ? id : null,
181
+ };
182
+ mountEditors();
183
+ return id;
184
+ }
185
+
186
+ /** Edit a cell's code as the document delivers it. */
187
+ function editCode(cellId: CellId, code: string) {
188
+ const cells = state.cellIds.map((id) => {
189
+ const cell = state.cellData[id];
190
+ return {
191
+ id,
192
+ code: id === cellId ? code : cell.code,
193
+ name: cell.name,
194
+ config: {
195
+ column: cell.config.column ?? null,
196
+ disabled: cell.config.disabled ?? false,
197
+ hide_code: cell.config.hide_code ?? false,
198
+ expand_output: cell.config.expand_output ?? false,
199
+ },
200
+ };
201
+ });
202
+ actions.reconcileDocument({
203
+ order: state.cellIds,
204
+ cells: new Map(cells.map((cell) => [cell.id, cell])),
205
+ });
206
+ }
140
207
 
141
208
  let i = 0;
142
209
  const originalCreate = CellId.create.bind(CellId);
@@ -151,878 +218,235 @@ describe("cell reducer", () => {
151
218
  i = 0;
152
219
 
153
220
  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();
221
+ seedCell({ cellId: "__end__", before: false });
222
+ firstCellId = state.cellIds[0];
161
223
  });
162
224
 
163
225
  afterAll(() => {
164
226
  CellId.create = originalCreate;
165
227
  });
166
228
 
167
- it("can add a cell after another cell", () => {
168
- actions.createNewCell({
169
- cellId: firstCellId,
170
- before: false,
171
- });
229
+ it("can update a cell", () => {
230
+ editCode(firstCellId, "import numpy as np");
172
231
  expect(formatCells(state)).toMatchInlineSnapshot(`
173
232
  "
174
- [0] ''
175
-
176
- [1] ''
233
+ [0] 'import numpy as np'
177
234
  "
178
235
  `);
179
236
  });
180
237
 
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,
238
+ it("keeps error evidence after an edit and a new queued run", () => {
239
+ const code = "raise ValueError('old failure')";
240
+ const output: OutputMessage = {
241
+ channel: "marimo-error",
242
+ mimetype: "application/vnd.marimo+error",
243
+ data: [
244
+ {
245
+ type: "exception",
246
+ msg: "old failure",
247
+ exception_type: "ValueError",
248
+ raising_cell: null,
249
+ traceback: "<pre>ValueError: old failure</pre>",
250
+ },
251
+ ],
252
+ code,
253
+ timestamp: 1,
254
+ };
255
+ actions.handleCellMessage({
256
+ cell_id: firstCellId,
257
+ output,
258
+ console: {
259
+ channel: "stderr",
260
+ mimetype: "application/vnd.marimo+traceback",
261
+ data: "<pre>ValueError: old failure</pre>",
262
+ code,
263
+ timestamp: 1,
264
+ },
265
+ status: "idle",
266
+ timestamp: 1,
186
267
  });
187
- expect(formatCells(state)).toMatchInlineSnapshot(`
188
- "
189
- [0] ''
268
+ editCode(firstCellId, "print('new code')");
269
+ actions.prepareForRun({ cellId: firstCellId });
270
+ actions.handleCellMessage({
271
+ cell_id: firstCellId,
272
+ output: null,
273
+ console: null,
274
+ status: "queued",
275
+ timestamp: 2,
276
+ });
277
+ const runtime = state.cellRuntime[firstCellId];
278
+ expect(runtime.output).toEqual(output);
279
+ expect(runtime.consoleOutputs[0].code).toBe(code);
280
+ expect(runtime.consoleOutputs[0].data).toContain("old failure");
281
+ });
190
282
 
191
- [1] 'import numpy as np'
192
- "
193
- `);
283
+ it("can run cell and receive cell messages", () => {
284
+ // HAPPY PATH
285
+ /////////////////
286
+ // Initial state
287
+ let cell = cells[0];
288
+ expect(cell).toMatchSnapshot("initial state");
194
289
 
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
- });
290
+ // Update code
291
+ editCode(firstCellId, "import marimo as mo");
292
+ cell = cells[0];
293
+ expect(cell).toMatchSnapshot("code edited");
200
294
 
201
- it("can add a cell before another cell", () => {
202
- actions.createNewCell({
295
+ // Prepare for run
296
+ actions.prepareForRun({
203
297
  cellId: firstCellId,
204
- before: true,
205
298
  });
206
- expect(formatCells(state)).toMatchInlineSnapshot(`
207
- "
208
- [1] ''
299
+ cell = cells[0];
300
+ expect(cell).toMatchSnapshot("prepared for run");
209
301
 
210
- [0] ''
211
- "
212
- `);
213
- });
302
+ // Receive queued messages
303
+ actions.handleCellMessage({
304
+ cell_id: firstCellId,
305
+ output: undefined,
306
+ console: null,
307
+ status: "queued",
308
+ timestamp: new Date(10).getTime() as Seconds,
309
+ });
310
+ cell = cells[0];
311
+ expect(cell).toMatchSnapshot("queued message received");
214
312
 
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 },
313
+ // Receive running messages
314
+ actions.handleCellMessage({
315
+ cell_id: firstCellId,
316
+ output: undefined,
317
+ console: null,
318
+ status: "running",
319
+ timestamp: new Date(20).getTime() as Seconds,
222
320
  });
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
- });
321
+ cell = cells[0];
322
+ expect(cell).toMatchSnapshot("running message received");
228
323
 
229
- it("can delete a Python cell and undo delete", () => {
230
- actions.createNewCell({
231
- cellId: firstCellId,
232
- before: false,
324
+ // Console messages shouldn't transition status
325
+ actions.handleCellMessage({
326
+ cell_id: firstCellId,
327
+ output: undefined,
328
+ console: {
329
+ channel: "stdout",
330
+ mimetype: "text/plain",
331
+ data: "hello!",
332
+ timestamp: 0,
333
+ },
334
+ status: undefined,
335
+ timestamp: new Date(22).getTime() as Seconds,
233
336
  });
234
- actions.deleteCell({
235
- cellId: firstCellId,
337
+ cell = cells[0];
338
+ expect(cell).toMatchSnapshot("console message received");
339
+
340
+ // Receive output messages
341
+ actions.handleCellMessage({
342
+ cell_id: firstCellId,
343
+ output: {
344
+ channel: "output",
345
+ mimetype: "text/plain",
346
+ data: "ok",
347
+ timestamp: 0,
348
+ },
349
+ console: {
350
+ channel: "stdout",
351
+ mimetype: "text/plain",
352
+ data: "hello!",
353
+ timestamp: 0,
354
+ },
355
+ status: "idle",
356
+ timestamp: new Date(33).getTime() as Seconds,
236
357
  });
237
- expect(formatCells(state)).toMatchInlineSnapshot(`
238
- "
239
- [1] ''
240
- "
241
- `);
358
+ cell = cells[0];
359
+ expect(cell).toMatchSnapshot("output received");
242
360
 
243
- // undo
244
- actions.undoDeleteCell();
245
- expect(formatCells(state)).toMatchInlineSnapshot(`
246
- "
247
- [2] ''
361
+ // EDITING BACK AND FORTH
362
+ /////////////////
363
+ // Update code again
364
+ editCode(firstCellId, "import marimo as mo\nimport numpy");
365
+ cell = cells[0];
366
+ expect(cell).toMatchSnapshot("code edited again");
248
367
 
249
- [1] ''
250
- "
251
- `);
368
+ // Update code should be unedited
369
+ editCode(firstCellId, "import marimo as mo");
370
+ cell = cells[0];
371
+ expect(cell).toMatchSnapshot("code reverted");
252
372
 
253
- // Verify scrollKey is set to the restored cell
254
- expect(state.scrollKey).toBe("2");
255
- });
373
+ // Update code should be edited again
374
+ editCode(firstCellId, "import marimo as mo\nimport numpy");
375
+ cell = cells[0];
376
+ expect(cell).toMatchSnapshot("code changed after revert");
256
377
 
257
- it("can delete a SQL cell and undo delete", () => {
258
- actions.createNewCell({
378
+ // ERROR RESPONSE
379
+ /////////////////
380
+ // Prepare for run
381
+ actions.prepareForRun({
259
382
  cellId: firstCellId,
260
- before: false,
261
- code: `df = mo.sql("""SELECT * FROM table""")`,
262
383
  });
263
- const newCellId = state.cellIds.getColumns()[0].atOrThrow(1);
264
- actions.deleteCell({
265
- cellId: newCellId,
384
+ cell = cells[0];
385
+ expect(cell.output).not.toBe(null); // keep old output
386
+ // Queue
387
+ actions.handleCellMessage({
388
+ cell_id: firstCellId,
389
+ output: undefined,
390
+ console: null,
391
+ status: "queued",
392
+ timestamp: new Date(40).getTime() as Seconds,
266
393
  });
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}""")`,
394
+ cell = cells[0];
395
+ expect(cell.output).not.toBe(null); // keep old output
396
+ // Running
397
+ actions.handleCellMessage({
398
+ cell_id: firstCellId,
399
+ output: undefined,
400
+ console: null,
401
+ status: "running",
402
+ timestamp: new Date(50).getTime() as Seconds,
290
403
  });
291
- const newCellId = state.cellIds.getColumns()[0].atOrThrow(1);
292
- actions.deleteCell({
293
- cellId: newCellId,
404
+ cell = cells[0];
405
+ expect(cell.output).not.toBe(null); // keep old output
406
+ // Receive error
407
+ actions.handleCellMessage({
408
+ cell_id: firstCellId,
409
+ output: {
410
+ channel: "marimo-error",
411
+ mimetype: "application/vnd.marimo+error",
412
+ data: [
413
+ { type: "exception", exception_type: "ValueError", msg: "Oh no!" },
414
+ ],
415
+ timestamp: 0,
416
+ },
417
+ console: null,
418
+ status: "idle",
419
+ timestamp: new Date(61).getTime() as Seconds,
294
420
  });
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
- });
421
+ cell = cells[0];
422
+ expect(cell.output).not.toBe(null); // keep old output
423
+ expect(cell).toMatchSnapshot("error response received");
311
424
 
312
- it("undo delete restores cell config (hide_code, disabled)", () => {
313
- actions.createNewCell({
425
+ // INTERRUPT RESPONSE
426
+ /////////////////
427
+ // Prepare for run
428
+ actions.prepareForRun({
314
429
  cellId: firstCellId,
315
- before: false,
316
- code: "x = 1",
317
430
  });
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 },
431
+ cell = cells[0];
432
+ expect(cell.output).not.toBe(null); // keep old output
433
+ // Queue
434
+ actions.handleCellMessage({
435
+ cell_id: firstCellId,
436
+ output: undefined,
437
+ console: null,
438
+ status: "queued",
439
+ timestamp: new Date(40).getTime() as Seconds,
324
440
  });
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("can run cell and receive cell messages", () => {
793
- // HAPPY PATH
794
- /////////////////
795
- // Initial state
796
- let cell = cells[0];
797
- expect(cell.status).toBe("idle");
798
- expect(cell.lastCodeRun).toBe(null);
799
- expect(cell.edited).toBe(false);
800
- expect(cell).toMatchSnapshot("initial state");
801
-
802
- // Update code
803
- actions.updateCellCode({
804
- cellId: firstCellId,
805
- code: "import marimo as mo",
806
- formattingChange: false,
807
- });
808
- cell = cells[0];
809
- expect(cell.status).toBe("idle");
810
- expect(cell.lastCodeRun).toBe(null);
811
- expect(cell.edited).toBe(true);
812
- expect(cell).toMatchSnapshot("code edited");
813
-
814
- // Prepare for run
815
- actions.prepareForRun({
816
- cellId: firstCellId,
817
- });
818
- cell = cells[0];
819
- expect(cell.status).toBe("queued");
820
- expect(cell.lastCodeRun).toBe("import marimo as mo");
821
- expect(cell.edited).toBe(false);
822
- expect(cell).toMatchSnapshot("prepared for run");
823
-
824
- // Receive queued messages
825
- actions.handleCellMessage({
826
- cell_id: firstCellId,
827
- output: undefined,
828
- console: null,
829
- status: "queued",
830
- stale_inputs: null,
831
- timestamp: new Date(10).getTime() as Seconds,
832
- });
833
- cell = cells[0];
834
- expect(cell.status).toBe("queued");
835
- expect(cell.lastCodeRun).toBe("import marimo as mo");
836
- expect(cell.edited).toBe(false);
837
- expect(cell.runElapsedTimeMs).toBe(null);
838
- expect(cell.runStartTimestamp).toBe(null);
839
- expect(cell.lastRunStartTimestamp).toBe(null);
840
- expect(cell).toMatchSnapshot("queued message received");
841
-
842
- // Receive running messages
843
- actions.handleCellMessage({
844
- cell_id: firstCellId,
845
- output: undefined,
846
- console: null,
847
- status: "running",
848
- stale_inputs: null,
849
- timestamp: new Date(20).getTime() as Seconds,
850
- });
851
- cell = cells[0];
852
- expect(cell.status).toBe("running");
853
- expect(cell.lastCodeRun).toBe("import marimo as mo");
854
- expect(cell.edited).toBe(false);
855
- expect(cell.runElapsedTimeMs).toBe(null);
856
- expect(cell.runStartTimestamp).toBe(20);
857
- expect(cell.lastRunStartTimestamp).toBe(20);
858
- expect(cell).toMatchSnapshot("running message received");
859
-
860
- // Console messages shouldn't transition status
861
- actions.handleCellMessage({
862
- cell_id: firstCellId,
863
- output: undefined,
864
- console: {
865
- channel: "stdout",
866
- mimetype: "text/plain",
867
- data: "hello!",
868
- timestamp: 0,
869
- },
870
- status: undefined,
871
- stale_inputs: null,
872
- timestamp: new Date(22).getTime() as Seconds,
873
- });
874
- cell = cells[0];
875
- expect(cell.status).toBe("running");
876
- expect(cell.edited).toBe(false);
877
- expect(cell.runElapsedTimeMs).toBe(null);
878
- expect(cell.runStartTimestamp).toBe(20);
879
- expect(cell.lastRunStartTimestamp).toBe(20);
880
- expect(cell).toMatchSnapshot("console message received");
881
-
882
- // Receive output messages
883
- actions.handleCellMessage({
884
- cell_id: firstCellId,
885
- output: {
886
- channel: "output",
887
- mimetype: "text/plain",
888
- data: "ok",
889
- timestamp: 0,
890
- },
891
- console: {
892
- channel: "stdout",
893
- mimetype: "text/plain",
894
- data: "hello!",
895
- timestamp: 0,
896
- },
897
- status: "idle",
898
- stale_inputs: null,
899
- timestamp: new Date(33).getTime() as Seconds,
900
- });
901
- cell = cells[0];
902
- expect(cell.status).toBe("idle");
903
- expect(cell.edited).toBe(false);
904
- expect(cell.runElapsedTimeMs).toBe(13_000);
905
- expect(cell.runStartTimestamp).toBe(null);
906
- expect(cell.lastRunStartTimestamp).toBe(20);
907
- expect(cell).toMatchSnapshot("output received");
908
-
909
- // EDITING BACK AND FORTH
910
- /////////////////
911
- // Update code again
912
- actions.updateCellCode({
913
- cellId: firstCellId,
914
- code: "import marimo as mo\nimport numpy",
915
- formattingChange: false,
916
- });
917
- cell = cells[0];
918
- expect(cell.status).toBe("idle");
919
- expect(cell.edited).toBe(true);
920
- expect(cell).toMatchSnapshot("code edited again");
921
-
922
- // Update code should be unedited
923
- actions.updateCellCode({
924
- cellId: firstCellId,
925
- code: "import marimo as mo",
926
- formattingChange: false,
927
- });
928
- cell = cells[0];
929
- expect(cell.edited).toBe(false);
930
- expect(cell).toMatchSnapshot("code reverted");
931
-
932
- // Update code should be edited again
933
- actions.updateCellCode({
934
- cellId: firstCellId,
935
- code: "import marimo as mo\nimport numpy",
936
- formattingChange: false,
937
- });
938
- cell = cells[0];
939
- expect(cell.status).toBe("idle");
940
- expect(cell.lastCodeRun).toBe("import marimo as mo");
941
- expect(cell.edited).toBe(true);
942
- expect(cell).toMatchSnapshot("code changed after revert");
943
-
944
- // ERROR RESPONSE
945
- /////////////////
946
- // Prepare for run
947
- actions.prepareForRun({
948
- cellId: firstCellId,
949
- });
950
- cell = cells[0];
951
- expect(cell.output).not.toBe(null); // keep old output
952
- // Queue
953
- actions.handleCellMessage({
954
- cell_id: firstCellId,
955
- output: undefined,
956
- console: null,
957
- status: "queued",
958
- stale_inputs: null,
959
- timestamp: new Date(40).getTime() as Seconds,
960
- });
961
- cell = cells[0];
962
- expect(cell.output).not.toBe(null); // keep old output
963
- // Running
964
- actions.handleCellMessage({
965
- cell_id: firstCellId,
966
- output: undefined,
967
- console: null,
968
- status: "running",
969
- stale_inputs: null,
970
- timestamp: new Date(50).getTime() as Seconds,
971
- });
972
- cell = cells[0];
973
- expect(cell.output).not.toBe(null); // keep old output
974
- // Receive error
975
- actions.handleCellMessage({
976
- cell_id: firstCellId,
977
- output: {
978
- channel: "marimo-error",
979
- mimetype: "application/vnd.marimo+error",
980
- data: [
981
- { type: "exception", exception_type: "ValueError", msg: "Oh no!" },
982
- ],
983
- timestamp: 0,
984
- },
985
- console: null,
986
- status: "idle",
987
- stale_inputs: null,
988
- timestamp: new Date(61).getTime() as Seconds,
989
- });
990
- cell = cells[0];
991
- expect(cell.output).not.toBe(null); // keep old output
992
- expect(cell.status).toBe("idle");
993
- expect(cell.lastCodeRun).toBe("import marimo as mo\nimport numpy");
994
- expect(cell.edited).toBe(false);
995
- expect(cell.runElapsedTimeMs).toBe(11_000);
996
- expect(cell.runStartTimestamp).toBe(null);
997
- expect(cell).toMatchSnapshot("error response received");
998
-
999
- // INTERRUPT RESPONSE
1000
- /////////////////
1001
- // Prepare for run
1002
- actions.prepareForRun({
1003
- cellId: firstCellId,
1004
- });
1005
- cell = cells[0];
1006
- expect(cell.output).not.toBe(null); // keep old output
1007
- // Queue
1008
- actions.handleCellMessage({
1009
- cell_id: firstCellId,
1010
- output: undefined,
1011
- console: null,
1012
- status: "queued",
1013
- stale_inputs: null,
1014
- timestamp: new Date(40).getTime() as Seconds,
1015
- });
1016
- cell = cells[0];
1017
- expect(cell.output).not.toBe(null); // keep old output
1018
- // Running
1019
- actions.handleCellMessage({
1020
- cell_id: firstCellId,
1021
- output: undefined,
1022
- console: null,
1023
- status: "running",
1024
- stale_inputs: null,
1025
- timestamp: new Date(50).getTime() as Seconds,
441
+ cell = cells[0];
442
+ expect(cell.output).not.toBe(null); // keep old output
443
+ // Running
444
+ actions.handleCellMessage({
445
+ cell_id: firstCellId,
446
+ output: undefined,
447
+ console: null,
448
+ status: "running",
449
+ timestamp: new Date(50).getTime() as Seconds,
1026
450
  });
1027
451
  cell = cells[0];
1028
452
  expect(cell.output).not.toBe(null); // keep old output
@@ -1037,100 +461,23 @@ describe("cell reducer", () => {
1037
461
  },
1038
462
  console: null,
1039
463
  status: "idle",
1040
- stale_inputs: null,
1041
464
  timestamp: new Date(61).getTime() as Seconds,
1042
465
  });
1043
466
  cell = cells[0];
1044
- expect(cell.status).toBe("idle");
1045
- expect(cell.lastCodeRun).toBe(cell.code);
1046
- expect(cell.edited).toBe(false);
1047
- expect(cell.runElapsedTimeMs).toBe(11_000);
1048
- expect(cell.runStartTimestamp).toBe(null);
1049
467
  expect(cell).toMatchSnapshot("interruption response received");
1050
468
  });
1051
469
 
1052
- it("errors reset status to idle", () => {
1053
- // Initial state
1054
- let cell = cells[0];
1055
- expect(cell.status).toBe("idle");
1056
- expect(cell.lastCodeRun).toBe(null);
1057
- expect(cell.edited).toBe(false);
1058
- expect(cell).toMatchSnapshot("initial state");
1059
-
1060
- // Update code
1061
- actions.updateCellCode({
1062
- cellId: firstCellId,
1063
- code: "import marimo as mo",
1064
- formattingChange: false,
1065
- });
1066
- cell = cells[0];
1067
- expect(cell.status).toBe("idle");
1068
- expect(cell.lastCodeRun).toBe(null);
1069
- expect(cell.edited).toBe(true);
1070
- expect(cell).toMatchSnapshot("code edited");
1071
-
1072
- // Prepare for run
1073
- actions.prepareForRun({
1074
- cellId: firstCellId,
1075
- });
1076
- cell = cells[0];
1077
- expect(cell.status).toBe("queued");
1078
- expect(cell.lastCodeRun).toBe("import marimo as mo");
1079
- expect(cell.edited).toBe(false);
1080
- expect(cell).toMatchSnapshot("prepared for run");
1081
-
1082
- // ERROR RESPONSE
1083
- //
1084
- // should reset status to idle
1085
- /////////////////
1086
- // Prepare for run
1087
- actions.prepareForRun({
1088
- cellId: firstCellId,
1089
- });
1090
- cell = cells[0];
1091
- // Receive error
1092
- actions.handleCellMessage({
1093
- cell_id: firstCellId,
1094
- output: {
1095
- channel: "marimo-error",
1096
- mimetype: "application/vnd.marimo+error",
1097
- data: [
1098
- { type: "exception", exception_type: "SyntaxError", msg: "Oh no!" },
1099
- ],
1100
- timestamp: 0,
1101
- },
1102
- console: null,
1103
- stale_inputs: null,
1104
- timestamp: new Date(61).getTime() as Seconds,
1105
- });
1106
- cell = cells[0];
1107
- expect(cell.status).toBe("idle");
1108
- expect(cell.lastCodeRun).toBe("import marimo as mo");
1109
- expect(cell.edited).toBe(false);
1110
- expect(cell.runElapsedTimeMs).toBe(null);
1111
- expect(cell.runStartTimestamp).toBe(null);
1112
- expect(cell).toMatchSnapshot("error response received");
1113
- });
1114
-
1115
470
  it("can run a stale cell", () => {
1116
471
  // Update code of first cell
1117
- actions.updateCellCode({
1118
- cellId: firstCellId,
1119
- code: "import marimo as mo",
1120
- formattingChange: false,
1121
- });
472
+ editCode(firstCellId, "import marimo as mo");
1122
473
  // Add cell
1123
- actions.createNewCell({
474
+ seedCell({
1124
475
  cellId: firstCellId,
1125
476
  before: false,
1126
477
  });
1127
478
  const secondCell = cells[1].id;
1128
479
  // Update code
1129
- actions.updateCellCode({
1130
- code: "mo.slider()",
1131
- cellId: secondCell,
1132
- formattingChange: false,
1133
- });
480
+ editCode(secondCell, "mo.slider()");
1134
481
 
1135
482
  // Prepare for run
1136
483
  actions.prepareForRun({
@@ -1143,15 +490,9 @@ describe("cell reducer", () => {
1143
490
  output: undefined,
1144
491
  console: null,
1145
492
  status: "queued",
1146
- stale_inputs: null,
1147
493
  timestamp: new Date(10).getTime() as Seconds,
1148
494
  });
1149
495
  let cell = cells[1];
1150
- expect(cell.status).toBe("queued");
1151
- expect(cell.lastCodeRun).toBe("mo.slider()");
1152
- expect(cell.edited).toBe(false);
1153
- expect(cell.runElapsedTimeMs).toBe(null);
1154
- expect(cell.runStartTimestamp).toBe(null);
1155
496
  expect(cell).toMatchSnapshot("queued message received");
1156
497
 
1157
498
  // Receive stale message
@@ -1160,105 +501,15 @@ describe("cell reducer", () => {
1160
501
  output: undefined,
1161
502
  console: null,
1162
503
  status: undefined,
1163
- stale_inputs: true,
1164
504
  timestamp: new Date(20).getTime() as Seconds,
1165
505
  });
1166
506
  cell = cells[1];
1167
- expect(cell.status).toBe("queued");
1168
- expect(cell.lastCodeRun).toBe("mo.slider()");
1169
- expect(cell.edited).toBe(false);
1170
- expect(cell.runElapsedTimeMs).toBe(null);
1171
- expect(cell.runStartTimestamp).toBe(null);
1172
507
  expect(cell).toMatchSnapshot("stale inputs received");
1173
508
  });
1174
509
 
1175
- it("can format code and update cell", () => {
1176
- const firstCellId = cells[0].id;
1177
- actions.updateCellCode({
1178
- cellId: firstCellId,
1179
- code: "import marimo as mo",
1180
- formattingChange: false,
1181
- });
1182
- let cell = cells[0];
1183
- expect(cell.status).toBe("idle");
1184
- expect(cell.lastCodeRun).toBe(null);
1185
- expect(cell.edited).toBe(true);
1186
-
1187
- // Run
1188
- actions.prepareForRun({
1189
- cellId: firstCellId,
1190
- });
1191
- actions.handleCellMessage({
1192
- cell_id: firstCellId,
1193
- output: {
1194
- channel: "output",
1195
- mimetype: "text/plain",
1196
- data: "ok",
1197
- timestamp: 0,
1198
- },
1199
- console: {
1200
- channel: "stdout",
1201
- mimetype: "text/plain",
1202
- data: "hello!",
1203
- timestamp: 0,
1204
- },
1205
- status: "idle",
1206
- stale_inputs: null,
1207
- timestamp: new Date(33).getTime() as Seconds,
1208
- });
1209
-
1210
- // Check steady state
1211
- cell = cells[0];
1212
- expect(cell.status).toBe("idle");
1213
- expect(cell.edited).toBe(false);
1214
- expect(cell.lastCodeRun).toBe("import marimo as mo");
1215
-
1216
- // Format code
1217
- actions.updateCellCode({
1218
- cellId: firstCellId,
1219
- code: "import marimo as mo",
1220
- formattingChange: true,
1221
- });
1222
- cell = cells[0];
1223
- expect(cell.status).toBe("idle");
1224
- expect(cell.lastCodeRun).toBe("import marimo as mo");
1225
- expect(cell.edited).toBe(false);
1226
- });
1227
-
1228
- it("can update a cells config", () => {
1229
- const firstCellId = cells[0].id;
1230
- let cell = cells[0];
1231
- // Starts empty
1232
- expect(cell.config).toEqual({
1233
- disabled: false,
1234
- hide_code: false,
1235
- column: null,
1236
- expand_output: false,
1237
- });
1238
-
1239
- actions.updateCellConfig({
1240
- cellId: firstCellId,
1241
- config: { disabled: true },
1242
- });
1243
- cell = cells[0];
1244
- expect(cell.config.disabled).toBe(true);
1245
-
1246
- // Revert
1247
- actions.updateCellConfig({
1248
- cellId: firstCellId,
1249
- config: { disabled: false },
1250
- });
1251
- cell = cells[0];
1252
- expect(cell.config.disabled).toBe(false);
1253
- });
1254
-
1255
510
  it("can run a stopped cell", () => {
1256
511
  // Update code of first cell
1257
- actions.updateCellCode({
1258
- cellId: firstCellId,
1259
- code: "mo.md('This has an ancestor that was stopped')",
1260
- formattingChange: false,
1261
- });
512
+ editCode(firstCellId, "mo.md('This has an ancestor that was stopped')");
1262
513
 
1263
514
  // Prepare for run
1264
515
  actions.prepareForRun({
@@ -1271,17 +522,9 @@ describe("cell reducer", () => {
1271
522
  output: undefined,
1272
523
  console: null,
1273
524
  status: "queued",
1274
- stale_inputs: null,
1275
525
  timestamp: new Date(10).getTime() as Seconds,
1276
526
  });
1277
527
  let cell = cells[0];
1278
- expect(cell.status).toBe("queued");
1279
- expect(cell.lastCodeRun).toBe(
1280
- "mo.md('This has an ancestor that was stopped')",
1281
- );
1282
- expect(cell.edited).toBe(false);
1283
- expect(cell.runElapsedTimeMs).toBe(null);
1284
- expect(cell.runStartTimestamp).toBe(null);
1285
528
  expect(cell).toMatchSnapshot("queued message received");
1286
529
 
1287
530
  // Receive idle message
@@ -1290,7 +533,6 @@ describe("cell reducer", () => {
1290
533
  output: undefined,
1291
534
  console: null,
1292
535
  status: "idle",
1293
- stale_inputs: null,
1294
536
  timestamp: new Date(20).getTime() as Seconds,
1295
537
  });
1296
538
  cell = cells[0];
@@ -1313,16 +555,13 @@ describe("cell reducer", () => {
1313
555
  },
1314
556
  console: null,
1315
557
  status: "idle",
1316
- stale_inputs: null,
1317
558
  timestamp: new Date(20).getTime() as Seconds,
1318
559
  });
1319
560
  cell = cells[0];
1320
- expect(cell.status).toBe("idle");
1321
561
  // oxlint-disable-next-line typescript/no-explicit-any no-unsafe-optional-chaining
1322
562
  expect((cell.output?.data as any)[0].msg).toBe(
1323
563
  "This cell wasn't run because an ancestor was stopped with `mo.stop`: ",
1324
564
  );
1325
- expect(cell.stopped).toBe(true);
1326
565
  expect(cell).toMatchSnapshot("ancestor stopped");
1327
566
 
1328
567
  // Receive queued message
@@ -1331,12 +570,9 @@ describe("cell reducer", () => {
1331
570
  output: undefined,
1332
571
  console: null,
1333
572
  status: "queued",
1334
- stale_inputs: null,
1335
573
  timestamp: new Date(30).getTime() as Seconds,
1336
574
  });
1337
575
  cell = cells[0];
1338
- expect(cell.status).toBe("queued");
1339
- expect(cell.stopped).toBe(true);
1340
576
  expect(cell).toMatchSnapshot("queued after stop");
1341
577
 
1342
578
  // Receive running message
@@ -1345,12 +581,9 @@ describe("cell reducer", () => {
1345
581
  output: undefined,
1346
582
  console: null,
1347
583
  status: "running",
1348
- stale_inputs: null,
1349
584
  timestamp: new Date(40).getTime() as Seconds,
1350
585
  });
1351
586
  cell = cells[0];
1352
- expect(cell.status).toBe("running");
1353
- expect(cell.stopped).toBe(false);
1354
587
  expect(cell.output).toBe(null);
1355
588
  expect(cell).toMatchSnapshot("running after stop");
1356
589
  });
@@ -1377,7 +610,6 @@ describe("cell reducer", () => {
1377
610
 
1378
611
  // Initial state
1379
612
  let cell = cells[0];
1380
- expect(cell.status).toBe("idle");
1381
613
  expect(cell.consoleOutputs).toEqual([]);
1382
614
  expect(cell).toMatchSnapshot("initial state");
1383
615
 
@@ -1386,7 +618,6 @@ describe("cell reducer", () => {
1386
618
  cellId: firstCellId,
1387
619
  });
1388
620
  cell = cells[0];
1389
- expect(cell.status).toBe("queued");
1390
621
  expect(cell.consoleOutputs).toEqual([]);
1391
622
  expect(cell).toMatchSnapshot("prepared for run");
1392
623
 
@@ -1396,11 +627,9 @@ describe("cell reducer", () => {
1396
627
  output: undefined,
1397
628
  console: null,
1398
629
  status: "queued",
1399
- stale_inputs: null,
1400
630
  timestamp: new Date(10).getTime() as Seconds,
1401
631
  });
1402
632
  cell = cells[0];
1403
- expect(cell.status).toBe("queued");
1404
633
  expect(cell).toMatchSnapshot("queued message received");
1405
634
 
1406
635
  // Receive running messages
@@ -1409,11 +638,9 @@ describe("cell reducer", () => {
1409
638
  output: undefined,
1410
639
  console: null,
1411
640
  status: "running",
1412
- stale_inputs: null,
1413
641
  timestamp: new Date(20).getTime() as Seconds,
1414
642
  });
1415
643
  cell = cells[0];
1416
- expect(cell.status).toBe("running");
1417
644
  expect(cell).toMatchSnapshot("running message received");
1418
645
 
1419
646
  // Add console
@@ -1422,16 +649,13 @@ describe("cell reducer", () => {
1422
649
  output: undefined,
1423
650
  console: STDOUT,
1424
651
  status: undefined,
1425
- stale_inputs: null,
1426
652
  timestamp: new Date(22).getTime() as Seconds,
1427
653
  });
1428
654
  cell = cells[0];
1429
- expect(cell.status).toBe("running");
1430
655
  expect(cell).toMatchSnapshot("stdout received");
1431
656
 
1432
657
  cell = cells[0];
1433
658
  expect(cell.consoleOutputs[0]).toMatchObject(STDOUT);
1434
- expect(cell.status).toBe("running");
1435
659
  expect(cell).toMatchSnapshot("stdout verified");
1436
660
 
1437
661
  // Ask via stdin
@@ -1440,13 +664,11 @@ describe("cell reducer", () => {
1440
664
  output: undefined,
1441
665
  console: STD_IN_1,
1442
666
  status: undefined,
1443
- stale_inputs: null,
1444
667
  timestamp: new Date(22).getTime() as Seconds,
1445
668
  });
1446
669
  cell = cells[0];
1447
670
  expect(cell.consoleOutputs[0]).toMatchObject(STDOUT);
1448
671
  expect(cell.consoleOutputs[1]).toMatchObject(STD_IN_1);
1449
- expect(cell.status).toBe("running");
1450
672
  expect(cell).toMatchSnapshot("stdin requested");
1451
673
 
1452
674
  // Response to stdin
@@ -1460,7 +682,6 @@ describe("cell reducer", () => {
1460
682
  ...STD_IN_1,
1461
683
  response: "Marimo!",
1462
684
  });
1463
- expect(cell.status).toBe("running");
1464
685
  expect(cell).toMatchSnapshot("stdin answered");
1465
686
 
1466
687
  // Ask via stdin, again
@@ -1469,7 +690,6 @@ describe("cell reducer", () => {
1469
690
  output: undefined,
1470
691
  console: STD_IN_2,
1471
692
  status: undefined,
1472
- stale_inputs: null,
1473
693
  timestamp: new Date(22).getTime() as Seconds,
1474
694
  });
1475
695
  cell = cells[0];
@@ -1478,7 +698,6 @@ describe("cell reducer", () => {
1478
698
  { ...STD_IN_1, response: "Marimo!" },
1479
699
  STD_IN_2,
1480
700
  ]);
1481
- expect(cell.status).toBe("running");
1482
701
  expect(cell).toMatchSnapshot("second stdin requested");
1483
702
 
1484
703
  // Interrupt, so we respond with ""
@@ -1492,11 +711,9 @@ describe("cell reducer", () => {
1492
711
  },
1493
712
  console: null,
1494
713
  status: "idle",
1495
- stale_inputs: null,
1496
714
  timestamp: new Date(61).getTime() as Seconds,
1497
715
  });
1498
716
  cell = cells[0];
1499
- expect(cell.status).toBe("idle");
1500
717
  expect(cell.consoleOutputs).toMatchObject([
1501
718
  STDOUT,
1502
719
  { ...STD_IN_1, response: "Marimo!" },
@@ -1516,7 +733,6 @@ describe("cell reducer", () => {
1516
733
  output: undefined,
1517
734
  console: prompt,
1518
735
  status: "running",
1519
- stale_inputs: null,
1520
736
  timestamp: 1 as Seconds,
1521
737
  });
1522
738
 
@@ -1527,7 +743,9 @@ describe("cell reducer", () => {
1527
743
  response: secret,
1528
744
  });
1529
745
 
1530
- expect(cells[0].consoleOutputs).toEqual([{ ...prompt, response: "" }]);
746
+ expect(cells[0].consoleOutputs).toEqual([
747
+ { ...prompt, id: 1, response: "" },
748
+ ]);
1531
749
  expect(JSON.stringify(cells[0].consoleOutputs)).not.toContain(secret);
1532
750
  });
1533
751
 
@@ -1546,7 +764,6 @@ describe("cell reducer", () => {
1546
764
  output: undefined,
1547
765
  console: null,
1548
766
  status: "running",
1549
- stale_inputs: null,
1550
767
  timestamp: new Date(20).getTime() as Seconds,
1551
768
  });
1552
769
  actions.handleCellMessage({
@@ -1554,7 +771,6 @@ describe("cell reducer", () => {
1554
771
  output: undefined,
1555
772
  console: STDOUT,
1556
773
  status: undefined,
1557
- stale_inputs: null,
1558
774
  timestamp: new Date(22).getTime() as Seconds,
1559
775
  });
1560
776
 
@@ -1588,7 +804,6 @@ describe("cell reducer", () => {
1588
804
 
1589
805
  // Initial state
1590
806
  let cell = cells[0];
1591
- expect(cell.status).toBe("idle");
1592
807
  expect(cell.consoleOutputs).toEqual([]);
1593
808
  expect(cell).toMatchSnapshot("initial state");
1594
809
 
@@ -1598,7 +813,6 @@ describe("cell reducer", () => {
1598
813
  output: undefined,
1599
814
  console: [OLD_STDOUT],
1600
815
  status: undefined,
1601
- stale_inputs: null,
1602
816
  timestamp: new Date(1).getTime() as Seconds,
1603
817
  });
1604
818
 
@@ -1607,7 +821,6 @@ describe("cell reducer", () => {
1607
821
  cellId: firstCellId,
1608
822
  });
1609
823
  cell = cells[0];
1610
- expect(cell.status).toBe("queued");
1611
824
  expect(cell.consoleOutputs).toMatchObject([OLD_STDOUT]); // Old stays there until it starts running
1612
825
  expect(cell).toMatchSnapshot("prepared for run");
1613
826
 
@@ -1617,11 +830,9 @@ describe("cell reducer", () => {
1617
830
  output: undefined,
1618
831
  console: null,
1619
832
  status: "queued",
1620
- stale_inputs: null,
1621
833
  timestamp: new Date(10).getTime() as Seconds,
1622
834
  });
1623
835
  cell = cells[0];
1624
- expect(cell.status).toBe("queued");
1625
836
  expect(cell.consoleOutputs).toMatchObject([OLD_STDOUT]); // Old stays there until it starts running
1626
837
  expect(cell).toMatchSnapshot("queued message received");
1627
838
 
@@ -1631,11 +842,9 @@ describe("cell reducer", () => {
1631
842
  output: undefined,
1632
843
  console: [], // Backend sends an empty array to clearu
1633
844
  status: "running",
1634
- stale_inputs: null,
1635
845
  timestamp: new Date(20).getTime() as Seconds,
1636
846
  });
1637
847
  cell = cells[0];
1638
- expect(cell.status).toBe("running");
1639
848
  expect(cell.consoleOutputs).toMatchObject([]);
1640
849
  expect(cell).toMatchSnapshot("console cleared while running");
1641
850
 
@@ -1644,611 +853,39 @@ describe("cell reducer", () => {
1644
853
  cell_id: firstCellId,
1645
854
  output: undefined,
1646
855
  console: [STDOUT],
1647
- status: "idle",
1648
- stale_inputs: null,
1649
- timestamp: new Date(22).getTime() as Seconds,
1650
- });
1651
- cell = cells[0];
1652
- expect(cell.consoleOutputs).toMatchObject([STDOUT]);
1653
- expect(cell.status).toBe("idle");
1654
- expect(cell).toMatchSnapshot("console received while idle");
1655
- });
1656
-
1657
- it("can send a cell to the top", () => {
1658
- actions.createNewCell({ cellId: firstCellId, before: false });
1659
- actions.createNewCell({ cellId: cellId("1"), before: false });
1660
- actions.sendToTop({ cellId: cellId("2") });
1661
- expect(formatCells(state)).toMatchInlineSnapshot(`
1662
- "
1663
- [2] ''
1664
-
1665
- [0] ''
1666
-
1667
- [1] ''
1668
- "
1669
- `);
1670
- });
1671
-
1672
- it("can send a cell to the bottom", () => {
1673
- actions.createNewCell({ cellId: firstCellId, before: false });
1674
- actions.createNewCell({ cellId: cellId("1"), before: false });
1675
- actions.sendToBottom({ cellId: firstCellId });
1676
- expect(formatCells(state)).toMatchInlineSnapshot(`
1677
- "
1678
- [1] ''
1679
-
1680
- [2] ''
1681
-
1682
- [0] ''
1683
- "
1684
- `);
1685
- });
1686
-
1687
- it("can focus cells", () => {
1688
- actions.createNewCell({ cellId: firstCellId, before: false });
1689
- actions.createNewCell({ cellId: cellId("1"), before: false });
1690
-
1691
- actions.focusCell({ cellId: cellId("1"), where: "before" });
1692
- expect(focusAndScrollCellIntoView).toHaveBeenCalledWith(
1693
- expect.objectContaining({
1694
- cellId: cellId("0"),
1695
- }),
1696
- );
1697
-
1698
- actions.focusTopCell();
1699
- expect(scrollToTop).toHaveBeenCalled();
1700
-
1701
- actions.focusBottomCell();
1702
- expect(scrollToBottom).toHaveBeenCalled();
1703
- });
1704
-
1705
- it("can update cell name", () => {
1706
- actions.updateCellName({ cellId: firstCellId, name: "Test Cell" });
1707
- expect(state.cellData[firstCellId].name).toBe("Test Cell");
1708
- });
1709
-
1710
- it("can set cell IDs and codes", () => {
1711
- const newIds = [cellId("3"), cellId("4"), cellId("5")];
1712
- const newCodes = ["code1", "code2", "code3"];
1713
-
1714
- actions.setCellIds({ cellIds: newIds });
1715
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).topLevelIds).toEqual(newIds);
1716
-
1717
- // When codeIsStale is false, lastCodeRun should match code
1718
- actions.setCellCodes({
1719
- codes: newCodes,
1720
- ids: newIds,
1721
- codeIsStale: false,
1722
- });
1723
- newIds.forEach((id, index) => {
1724
- expect(state.cellData[id].code).toBe(newCodes[index]);
1725
- expect(state.cellData[id].lastCodeRun).toBe(newCodes[index]);
1726
- expect(state.cellData[id].edited).toBe(false);
1727
- });
1728
-
1729
- // When codeIsStale is true, lastCodeRun should not change
1730
- const staleCodes = ["stale1", "stale2", "stale3"];
1731
- actions.setCellCodes({
1732
- codes: staleCodes,
1733
- ids: newIds,
1734
- codeIsStale: true,
1735
- });
1736
- newIds.forEach((id, index) => {
1737
- expect(state.cellData[id].code).toBe(staleCodes[index]);
1738
- expect(state.cellData[id].lastCodeRun).toBe(newCodes[index]);
1739
- expect(state.cellData[id].edited).toBe(true);
1740
- });
1741
- });
1742
-
1743
- it("can can add a new cell with/without stale code", () => {
1744
- actions.setCellCodes({
1745
- codes: ["new code"],
1746
- ids: [cellId("2")],
1747
- codeIsStale: false,
1748
- });
1749
-
1750
- expect(state.cellData[cellId("2")].code).toBe("new code");
1751
- expect(state.cellData[cellId("2")].edited).toBe(false);
1752
- expect(state.cellData[cellId("2")].lastCodeRun).toBe("new code");
1753
-
1754
- actions.setCellCodes({
1755
- codes: ["new code 2"],
1756
- ids: [cellId("9")],
1757
- codeIsStale: true,
1758
- });
1759
-
1760
- expect(state.cellData[cellId("9")].code).toBe("new code 2");
1761
- expect(state.cellData[cellId("9")].edited).toBe(true);
1762
- expect(state.cellData[cellId("9")].lastCodeRun).toBe(null);
1763
- });
1764
-
1765
- it("can partial update cell codes", () => {
1766
- actions.createNewCell({ cellId: firstCellId, before: false });
1767
- actions.createNewCell({ cellId: cellId("1"), before: false });
1768
-
1769
- expect(state.cellIds.inOrderIds).toEqual([
1770
- cellId("0"),
1771
- cellId("1"),
1772
- cellId("2"),
1773
- ]);
1774
- expect(state.cellData[cellId("0")].code).toBe("");
1775
- expect(state.cellData[cellId("1")].code).toBe("");
1776
- expect(state.cellData[cellId("2")].code).toBe("");
1777
-
1778
- // Update cell 1
1779
- actions.setCellCodes({
1780
- codes: ["new code 2"],
1781
- ids: [cellId("1")],
1782
- codeIsStale: false,
1783
- });
1784
-
1785
- expect(state.cellIds.inOrderIds).toEqual([
1786
- cellId("0"),
1787
- cellId("1"),
1788
- cellId("2"),
1789
- ]);
1790
- expect(state.cellData[cellId("0")].code).toBe("");
1791
- expect(state.cellData[cellId("1")].code).toBe("new code 2");
1792
- expect(state.cellData[cellId("1")].edited).toBe(false);
1793
- expect(state.cellData[cellId("2")].code).toBe("");
1794
- });
1795
-
1796
- it("can set cell codes with new cell ids, while preserving the old cell data", () => {
1797
- actions.setCellCodes({
1798
- codes: ["code1", "code2", "code3"],
1799
- ids: [cellId("3"), cellId("4"), cellId("5")],
1800
- codeIsStale: false,
1801
- });
1802
- expect(state.cellData[cellId("3")].code).toBe("code1");
1803
- expect(state.cellData[cellId("4")].code).toBe("code2");
1804
- expect(state.cellData[cellId("5")].code).toBe("code3");
1805
-
1806
- // Update with some new cell ids and some old cell ids
1807
- actions.setCellIds({
1808
- cellIds: [cellId("1"), cellId("2"), cellId("3"), cellId("4")],
1809
- });
1810
- actions.setCellCodes({
1811
- codes: ["new1", "new2", "code1", "code2"],
1812
- ids: [cellId("1"), cellId("2"), cellId("3"), cellId("4")],
1813
- codeIsStale: false,
1814
- });
1815
- expect(state.cellData[cellId("1")].code).toBe("new1");
1816
- expect(state.cellData[cellId("2")].code).toBe("new2");
1817
- expect(state.cellData[cellId("3")].code).toBe("code1");
1818
- expect(state.cellData[cellId("4")].code).toBe("code2");
1819
- expect(state.cellIds.inOrderIds).toEqual([
1820
- cellId("1"),
1821
- cellId("2"),
1822
- cellId("3"),
1823
- cellId("4"),
1824
- ]);
1825
- // Cell 5 data is preserved (possibly used for tracing), but it's not in the cellIds
1826
- expect(state.cellData[cellId("5")]).not.toBeUndefined();
1827
- });
1828
-
1829
- it("can set cell codes with names and configs", () => {
1830
- const newIds = [cellId("3"), cellId("4")];
1831
- actions.setCellIds({ cellIds: newIds });
1832
- actions.setCellCodes({
1833
- codes: ["code1", "code2"],
1834
- ids: newIds,
1835
- codeIsStale: false,
1836
- names: ["setup_cell", "analysis"],
1837
- configs: [
1838
- { hide_code: true, disabled: false, column: null },
1839
- { hide_code: false, disabled: true, column: null },
1840
- ],
1841
- });
1842
-
1843
- expect(state.cellData[cellId("3")].name).toBe("setup_cell");
1844
- expect(state.cellData[cellId("3")].config.hide_code).toBe(true);
1845
- expect(state.cellData[cellId("3")].config.disabled).toBe(false);
1846
-
1847
- expect(state.cellData[cellId("4")].name).toBe("analysis");
1848
- expect(state.cellData[cellId("4")].config.hide_code).toBe(false);
1849
- expect(state.cellData[cellId("4")].config.disabled).toBe(true);
1850
- });
1851
-
1852
- it("can set cell codes without names/configs (backward compat)", () => {
1853
- const newIds = [cellId("3")];
1854
- actions.setCellIds({ cellIds: newIds });
1855
- actions.setCellCodes({
1856
- codes: ["code1"],
1857
- ids: newIds,
1858
- codeIsStale: false,
1859
- });
1860
-
1861
- // Should use defaults when names/configs not provided
1862
- expect(state.cellData[cellId("3")].code).toBe("code1");
1863
- expect(state.cellData[cellId("3")].config.hide_code).toBe(false);
1864
- expect(state.cellData[cellId("3")].config.disabled).toBe(false);
1865
- });
1866
-
1867
- it("can update names and configs on existing cells via setCellCodes", () => {
1868
- // Set initial state
1869
- actions.setCellCodes({
1870
- codes: ["x = 1"],
1871
- ids: [firstCellId],
1872
- codeIsStale: false,
1873
- names: ["old_name"],
1874
- configs: [{ hide_code: false, disabled: false, column: null }],
1875
- });
1876
- expect(state.cellData[firstCellId].name).toBe("old_name");
1877
- expect(state.cellData[firstCellId].config.hide_code).toBe(false);
1878
-
1879
- // Update with new name and config (same code)
1880
- actions.setCellCodes({
1881
- codes: ["x = 1"],
1882
- ids: [firstCellId],
1883
- codeIsStale: true,
1884
- names: ["new_name"],
1885
- configs: [{ hide_code: true, disabled: false, column: null }],
1886
- });
1887
- expect(state.cellData[firstCellId].name).toBe("new_name");
1888
- expect(state.cellData[firstCellId].config.hide_code).toBe(true);
1889
- });
1890
-
1891
- it("can fold and unfold all cells", () => {
1892
- actions.foldAll();
1893
- expect(foldAllBulk).toHaveBeenCalled();
1894
-
1895
- actions.unfoldAll();
1896
- expect(unfoldAllBulk).toHaveBeenCalled();
1897
- });
1898
-
1899
- it("can clear logs", () => {
1900
- state.cellLogs = [
1901
- {
1902
- level: "stderr",
1903
- message: "log1",
1904
- timestamp: 0,
1905
- source: { type: "cell", cellId: firstCellId },
1906
- },
1907
- {
1908
- level: "stderr",
1909
- message: "log1",
1910
- timestamp: 0,
1911
- source: { type: "cell", cellId: firstCellId },
1912
- },
1913
- ];
1914
- actions.clearLogs();
1915
- expect(state.cellLogs).toEqual([]);
1916
- });
1917
-
1918
- it("can collapse and expand cells", () => {
1919
- actions.createNewCell({ cellId: firstCellId, before: false });
1920
- actions.createNewCell({
1921
- cellId: cellId("1"),
1922
- before: false,
1923
- code: "# Header",
1924
- });
1925
- actions.createNewCell({
1926
- cellId: cellId("2"),
1927
- before: false,
1928
- code: "## Subheader",
1929
- });
1930
-
1931
- const id = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
1932
- state.cellRuntime[id] = {
1933
- ...state.cellRuntime[id],
1934
- outline: {
1935
- items: [{ name: "Header", level: 1, by: { id: "header" } }],
1936
- },
1937
- };
1938
-
1939
- actions.collapseCell({ cellId: id });
1940
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(id)).toBe(true);
1941
-
1942
- actions.expandCell({ cellId: id });
1943
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(id)).toBe(false);
1944
- });
1945
-
1946
- it("can collapse and expand all cells in multiple columns", () => {
1947
- actions.createNewCell({ cellId: firstCellId, before: false });
1948
- actions.createNewCell({
1949
- cellId: cellId("1"),
1950
- before: false,
1951
- code: "# First Column Header",
1952
- });
1953
- actions.createNewCell({
1954
- cellId: cellId("2"),
1955
- before: false,
1956
- code: "## First Column Subheader",
1957
- });
1958
-
1959
- actions.addColumnBreakpoint({ cellId: cellId("2") });
1960
-
1961
- actions.createNewCell({
1962
- cellId: cellId("3"),
1963
- before: false,
1964
- code: "# Second Column Header",
1965
- });
1966
- actions.createNewCell({
1967
- cellId: cellId("4"),
1968
- before: false,
1969
- code: "## Second Column Subheader",
1970
- });
1971
-
1972
- const firstColumnHeaderId = state.cellIds
1973
- .atOrThrow(FIRST_COLUMN)
1974
- .atOrThrow(1);
1975
- state.cellRuntime[firstColumnHeaderId] = {
1976
- ...state.cellRuntime[firstColumnHeaderId],
1977
- outline: {
1978
- items: [
1979
- { name: "First Column Header", level: 1, by: { id: "header" } },
1980
- ],
1981
- },
1982
- };
1983
-
1984
- const secondColumnHeaderId = state.cellIds
1985
- .atOrThrow(SECOND_COLUMN)
1986
- .atOrThrow(0);
1987
- state.cellRuntime[secondColumnHeaderId] = {
1988
- ...state.cellRuntime[secondColumnHeaderId],
1989
- outline: {
1990
- items: [
1991
- { name: "Second Column Header", level: 1, by: { id: "header" } },
1992
- ],
1993
- },
1994
- };
1995
-
1996
- actions.collapseAllCells();
1997
- expect(
1998
- state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(firstColumnHeaderId),
1999
- ).toBe(true);
2000
- expect(
2001
- state.cellIds.atOrThrow(SECOND_COLUMN).isCollapsed(secondColumnHeaderId),
2002
- ).toBe(true);
2003
-
2004
- actions.expandAllCells();
2005
- expect(
2006
- state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(firstColumnHeaderId),
2007
- ).toBe(false);
2008
- expect(
2009
- state.cellIds.atOrThrow(SECOND_COLUMN).isCollapsed(secondColumnHeaderId),
2010
- ).toBe(false);
2011
- });
2012
-
2013
- it("can collapse and expand nested cells in one call", () => {
2014
- actions.createNewCell({ cellId: firstCellId, before: false });
2015
- actions.createNewCell({
2016
- cellId: cellId("1"),
2017
- before: false,
2018
- code: "# Header",
2019
- });
2020
- actions.createNewCell({
2021
- cellId: cellId("2"),
2022
- before: false,
2023
- code: "## Subheader",
2024
- });
2025
- actions.createNewCell({
2026
- cellId: cellId("3"),
2027
- before: false,
2028
- code: "### Subsubheader",
2029
- });
2030
-
2031
- const headerId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
2032
- state.cellRuntime[headerId] = {
2033
- ...state.cellRuntime[headerId],
2034
- outline: {
2035
- items: [{ name: "Header", level: 1, by: { id: "header" } }],
2036
- },
2037
- };
2038
-
2039
- const subheaderId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(2);
2040
- state.cellRuntime[subheaderId] = {
2041
- ...state.cellRuntime[subheaderId],
2042
- outline: {
2043
- items: [{ name: "Subheader", level: 2, by: { id: "subheader" } }],
2044
- },
2045
- };
2046
-
2047
- // Check if both the parent and child are collapsed
2048
- actions.collapseAllCells();
2049
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(headerId)).toBe(
2050
- true,
2051
- );
2052
- actions.expandCell({ cellId: headerId });
2053
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(subheaderId)).toBe(
2054
- true,
2055
- );
2056
- actions.collapseCell({ cellId: headerId });
2057
-
2058
- // Check if both the parent and child are expanded
2059
- actions.expandAllCells();
2060
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(headerId)).toBe(
2061
- false,
2062
- );
2063
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(subheaderId)).toBe(
2064
- false,
2065
- );
2066
- });
2067
-
2068
- it("can collapse all cells when nested heading ranges end at the same cell", () => {
2069
- actions.createNewCell({ cellId: firstCellId, before: false });
2070
- actions.createNewCell({
2071
- cellId: cellId("1"),
2072
- before: false,
2073
- code: "# Header",
2074
- });
2075
- actions.createNewCell({
2076
- cellId: cellId("2"),
2077
- before: false,
2078
- code: "## Subheader",
2079
- });
2080
- actions.createNewCell({
2081
- cellId: cellId("3"),
2082
- before: false,
2083
- code: "### Subsubheader",
2084
- });
2085
-
2086
- const headerId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
2087
- state.cellRuntime[headerId] = {
2088
- ...state.cellRuntime[headerId],
2089
- outline: {
2090
- items: [{ name: "Header", level: 1, by: { id: "header" } }],
2091
- },
2092
- };
2093
-
2094
- const subheaderId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(2);
2095
- state.cellRuntime[subheaderId] = {
2096
- ...state.cellRuntime[subheaderId],
2097
- outline: {
2098
- items: [{ name: "Subheader", level: 2, by: { id: "subheader" } }],
2099
- },
2100
- };
2101
-
2102
- const subsubheaderId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(3);
2103
- state.cellRuntime[subsubheaderId] = {
2104
- ...state.cellRuntime[subsubheaderId],
2105
- outline: {
2106
- items: [{ name: "Subsubheader", level: 3, by: { id: "subsubheader" } }],
2107
- },
2108
- };
2109
-
2110
- // The header, subheader and subsubheader ranges all end at the last
2111
- // cell. Collapsing all used to throw "Node ... not found in tree" and
2112
- // leave the whole notebook unchanged.
2113
- actions.collapseAllCells();
2114
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(headerId)).toBe(
2115
- true,
2116
- );
2117
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).topLevelIds).toEqual([
2118
- firstCellId,
2119
- headerId,
2120
- ]);
2121
-
2122
- // Each level is nested under its parent, collapsed
2123
- actions.expandCell({ cellId: headerId });
2124
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(subheaderId)).toBe(
2125
- true,
2126
- );
2127
- actions.expandCell({ cellId: subheaderId });
2128
- expect(
2129
- state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(subsubheaderId),
2130
- ).toBe(true);
2131
- });
2132
-
2133
- it("can collapse all cells when a deep chain is followed by a later section", () => {
2134
- actions.createNewCell({ cellId: firstCellId, before: false });
2135
- actions.createNewCell({
2136
- cellId: cellId("1"),
2137
- before: false,
2138
- code: "# Header",
2139
- });
2140
- actions.createNewCell({
2141
- cellId: cellId("2"),
2142
- before: false,
2143
- code: "## Section A",
2144
- });
2145
- actions.createNewCell({
2146
- cellId: cellId("3"),
2147
- before: false,
2148
- code: "### Sub of A",
2149
- });
2150
- actions.createNewCell({
2151
- cellId: cellId("4"),
2152
- before: false,
2153
- code: "## Section B",
2154
- });
2155
-
2156
- const headerId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
2157
- state.cellRuntime[headerId] = {
2158
- ...state.cellRuntime[headerId],
2159
- outline: {
2160
- items: [{ name: "Header", level: 1, by: { id: "header" } }],
2161
- },
2162
- };
2163
-
2164
- const sectionAId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(2);
2165
- state.cellRuntime[sectionAId] = {
2166
- ...state.cellRuntime[sectionAId],
2167
- outline: {
2168
- items: [{ name: "Section A", level: 2, by: { id: "section-a" } }],
2169
- },
2170
- };
2171
-
2172
- const subsectionId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(3);
2173
- state.cellRuntime[subsectionId] = {
2174
- ...state.cellRuntime[subsectionId],
2175
- outline: {
2176
- items: [{ name: "Sub of A", level: 3, by: { id: "sub-of-a" } }],
2177
- },
2178
- };
2179
-
2180
- const sectionBId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(4);
2181
- state.cellRuntime[sectionBId] = {
2182
- ...state.cellRuntime[sectionBId],
2183
- outline: {
2184
- items: [{ name: "Section B", level: 2, by: { id: "section-b" } }],
2185
- },
2186
- };
2187
-
2188
- actions.collapseAllCells();
2189
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(headerId)).toBe(
2190
- true,
2191
- );
2192
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).topLevelIds).toEqual([
2193
- firstCellId,
2194
- headerId,
2195
- ]);
2196
-
2197
- // Both sections sit under the header, collapsed, with the subsection
2198
- // nested under section A
2199
- actions.expandCell({ cellId: headerId });
2200
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(sectionAId)).toBe(
2201
- true,
2202
- );
2203
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(sectionBId)).toBe(
2204
- true,
2205
- );
2206
- actions.expandCell({ cellId: sectionAId });
2207
- expect(
2208
- state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(subsectionId),
2209
- ).toBe(true);
856
+ status: "idle",
857
+ timestamp: new Date(22).getTime() as Seconds,
858
+ });
859
+ cell = cells[0];
860
+ expect(cell.consoleOutputs).toMatchObject([STDOUT]);
861
+ expect(cell).toMatchSnapshot("console received while idle");
2210
862
  });
2211
863
 
2212
- it("can show hidden cells", () => {
2213
- actions.createNewCell({ cellId: firstCellId, before: false });
2214
- actions.createNewCell({ cellId: cellId("1"), before: false });
2215
- actions.collapseCell({ cellId: firstCellId });
864
+ it("can fold and unfold all cells", () => {
865
+ actions.foldAll();
866
+ expect(foldAllBulk).toHaveBeenCalled();
2216
867
 
2217
- actions.showCellIfHidden({ cellId: cellId("1") });
2218
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).isCollapsed(firstCellId)).toBe(
2219
- false,
2220
- );
868
+ actions.unfoldAll();
869
+ expect(unfoldAllBulk).toHaveBeenCalled();
2221
870
  });
2222
871
 
2223
- it("can split and undo split cells", () => {
2224
- actions.createNewCell({
2225
- cellId: firstCellId,
2226
- before: false,
2227
- code: "line1\nline2",
2228
- });
2229
- const nextCellId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
2230
-
2231
- const originalCellCount = state.cellIds.atOrThrow(FIRST_COLUMN).length;
2232
- // Move cursor to the second line
2233
- const editor = state.cellHandles[nextCellId].current?.editorView;
2234
- if (!editor) {
2235
- throw new Error("Editor not found");
2236
- }
2237
- editor.dispatch({ selection: { anchor: 5, head: 5 } });
2238
- actions.splitCell({ cellId: nextCellId });
2239
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).length).toBe(
2240
- originalCellCount + 1,
2241
- );
2242
- expect(state.cellData[nextCellId].code).toBe("line1");
2243
- expect(
2244
- state.cellData[state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(2)].code,
2245
- ).toBe("line2");
2246
-
2247
- actions.undoSplitCell({ cellId: nextCellId, snapshot: "line1\nline2" });
2248
- expect(state.cellIds.atOrThrow(FIRST_COLUMN).length).toBe(
2249
- originalCellCount,
2250
- );
2251
- expect(state.cellData[nextCellId].code).toBe("line1\nline2");
872
+ it("can clear logs", () => {
873
+ state.cellLogs = [
874
+ {
875
+ level: "stderr",
876
+ message: "log1",
877
+ timestamp: 0,
878
+ source: { type: "cell", cellId: firstCellId },
879
+ },
880
+ {
881
+ level: "stderr",
882
+ message: "log1",
883
+ timestamp: 0,
884
+ source: { type: "cell", cellId: firstCellId },
885
+ },
886
+ ];
887
+ actions.clearLogs();
888
+ expect(state.cellLogs).toEqual([]);
2252
889
  });
2253
890
 
2254
891
  it("can handle multiple console outputs", () => {
@@ -2270,7 +907,6 @@ describe("cell reducer", () => {
2270
907
  output: undefined,
2271
908
  console: [STDOUT1, STDOUT2],
2272
909
  status: "running",
2273
- stale_inputs: null,
2274
910
  timestamp: new Date(20).getTime() as Seconds,
2275
911
  });
2276
912
 
@@ -2287,407 +923,6 @@ describe("cell reducer", () => {
2287
923
  `);
2288
924
  });
2289
925
 
2290
- it("can add a column breakpoint", () => {
2291
- actions.createNewCell({ cellId: firstCellId, before: false });
2292
- actions.createNewCell({ cellId: cellId("1"), before: false });
2293
- actions.createNewCell({ cellId: cellId("2"), before: false });
2294
-
2295
- expect(state.cellIds.getColumns().length).toBe(1);
2296
- expect(formatCells(state)).toMatchInlineSnapshot(`
2297
- "
2298
- [0] ''
2299
-
2300
- [1] ''
2301
-
2302
- [2] ''
2303
-
2304
- [3] ''
2305
- "
2306
- `);
2307
-
2308
- actions.addColumnBreakpoint({ cellId: cellId("2") });
2309
-
2310
- expect(state.cellIds.getColumns().length).toBe(2);
2311
- expect(formatCells(state)).toMatchInlineSnapshot(`
2312
- "
2313
- > col 0
2314
- [0] ''
2315
-
2316
- [1] ''
2317
-
2318
- > col 1
2319
- [2] ''
2320
-
2321
- [3] ''
2322
- "
2323
- `);
2324
-
2325
- // Check that the cells are in the correct columns
2326
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
2327
- cellId("0"),
2328
- cellId("1"),
2329
- ]);
2330
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([
2331
- cellId("2"),
2332
- cellId("3"),
2333
- ]);
2334
- });
2335
-
2336
- it("cannot add a column breakpoint before the first cell", () => {
2337
- expect(state.cellIds.getColumns().length).toBe(1);
2338
- actions.createNewCell({ cellId: firstCellId, before: false });
2339
- actions.createNewCell({ cellId: cellId("1"), before: false });
2340
- actions.addColumnBreakpoint({ cellId: firstCellId });
2341
- expect(state.cellIds.getColumns().length).toBe(1);
2342
- });
2343
-
2344
- it("can delete a column", () => {
2345
- actions.createNewCell({ cellId: firstCellId, before: false });
2346
- actions.createNewCell({ cellId: cellId("1"), before: false });
2347
- actions.createNewCell({ cellId: cellId("2"), before: false });
2348
- actions.addColumnBreakpoint({ cellId: cellId("2") });
2349
-
2350
- expect(state.cellIds.getColumns().length).toBe(2);
2351
- expect(formatCells(state)).toMatchInlineSnapshot(`
2352
- "
2353
- > col 0
2354
- [0] ''
2355
-
2356
- [1] ''
2357
-
2358
- > col 1
2359
- [2] ''
2360
-
2361
- [3] ''
2362
- "
2363
- `);
2364
-
2365
- const columnId = state.cellIds.atOrThrow(0).id;
2366
- actions.deleteColumn({ columnId: columnId });
2367
-
2368
- expect(state.cellIds.getColumns().length).toBe(1);
2369
- expect(formatCells(state)).toMatchInlineSnapshot(`
2370
- "
2371
- [2] ''
2372
-
2373
- [3] ''
2374
-
2375
- [0] ''
2376
-
2377
- [1] ''
2378
- "
2379
- `);
2380
-
2381
- // Check that all cells are now in the remaining column
2382
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
2383
- "2",
2384
- "3",
2385
- "0",
2386
- "1",
2387
- ]);
2388
- });
2389
-
2390
- it("deleting the last column does nothing", () => {
2391
- actions.createNewCell({ cellId: firstCellId, before: false });
2392
- actions.createNewCell({ cellId: cellId("1"), before: false });
2393
-
2394
- const initialState = { ...state };
2395
-
2396
- actions.deleteColumn({ columnId: initialState.cellIds.atOrThrow(0).id });
2397
-
2398
- // State should not change
2399
- expect(state).toEqual(initialState);
2400
- });
2401
-
2402
- it("can drop a cell over another cell", () => {
2403
- actions.createNewCell({ cellId: firstCellId, before: false });
2404
- actions.createNewCell({ cellId: cellId("1"), before: false });
2405
- actions.createNewCell({ cellId: cellId("2"), before: false });
2406
-
2407
- expect(formatCells(state)).toMatchInlineSnapshot(`
2408
- "
2409
- [0] ''
2410
-
2411
- [1] ''
2412
-
2413
- [2] ''
2414
-
2415
- [3] ''
2416
- "
2417
- `);
2418
-
2419
- actions.dropCellOverCell({
2420
- cellId: cellId("0"),
2421
- overCellId: cellId("3"),
2422
- });
2423
-
2424
- expect(formatCells(state)).toMatchInlineSnapshot(`
2425
- "
2426
- [1] ''
2427
-
2428
- [2] ''
2429
-
2430
- [3] ''
2431
-
2432
- [0] ''
2433
- "
2434
- `);
2435
- });
2436
-
2437
- it("can drop a cell over a new column", () => {
2438
- actions.createNewCell({ cellId: firstCellId, before: false });
2439
- actions.createNewCell({ cellId: cellId("1"), before: false });
2440
-
2441
- expect(state.cellIds.getColumns().length).toBe(1);
2442
- expect(formatCells(state)).toMatchInlineSnapshot(`
2443
- "
2444
- [0] ''
2445
-
2446
- [1] ''
2447
-
2448
- [2] ''
2449
- "
2450
- `);
2451
-
2452
- actions.dropOverNewColumn({ cellId: cellId("1") });
2453
-
2454
- expect(state.cellIds.getColumns().length).toBe(2);
2455
- expect(formatCells(state)).toMatchInlineSnapshot(`
2456
- "
2457
- > col 0
2458
- [0] ''
2459
-
2460
- [2] ''
2461
-
2462
- > col 1
2463
- [1] ''
2464
- "
2465
- `);
2466
-
2467
- // Check that the cells are in the correct columns
2468
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
2469
- cellId("0"),
2470
- cellId("2"),
2471
- ]);
2472
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([cellId("1")]);
2473
- });
2474
-
2475
- it("can drop a column over another column", () => {
2476
- actions.createNewCell({ cellId: firstCellId, before: false });
2477
- actions.createNewCell({ cellId: cellId("1"), before: false });
2478
- actions.createNewCell({ cellId: cellId("2"), before: false });
2479
- actions.addColumnBreakpoint({ cellId: cellId("2") });
2480
-
2481
- expect(state.cellIds.getColumns().length).toBe(2);
2482
- expect(formatCells(state)).toMatchInlineSnapshot(`
2483
- "
2484
- > col 0
2485
- [0] ''
2486
-
2487
- [1] ''
2488
-
2489
- > col 1
2490
- [2] ''
2491
-
2492
- [3] ''
2493
- "
2494
- `);
2495
-
2496
- const columnId0 = state.cellIds.atOrThrow(0).id;
2497
- const columnId1 = state.cellIds.atOrThrow(1).id;
2498
- expect(columnId0).not.toBe(columnId1);
2499
- actions.moveColumn({ column: columnId1, overColumn: columnId0 });
2500
-
2501
- expect(state.cellIds.getColumns().length).toBe(2);
2502
- expect(formatCells(state)).toMatchInlineSnapshot(`
2503
- "
2504
- > col 0
2505
- [2] ''
2506
-
2507
- [3] ''
2508
-
2509
- > col 1
2510
- [0] ''
2511
-
2512
- [1] ''
2513
- "
2514
- `);
2515
-
2516
- // Check that the columns have swapped positions
2517
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
2518
- cellId("2"),
2519
- cellId("3"),
2520
- ]);
2521
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([
2522
- cellId("0"),
2523
- cellId("1"),
2524
- ]);
2525
- });
2526
-
2527
- it("can compact columns", () => {
2528
- // Create initial state with 3 columns, including an empty one
2529
- actions.createNewCell({ cellId: firstCellId, before: false });
2530
- actions.createNewCell({ cellId: cellId("1"), before: false });
2531
- actions.addColumnBreakpoint({ cellId: cellId("1") });
2532
- actions.addColumnBreakpoint({ cellId: cellId("2") });
2533
- actions.dropOverNewColumn({ cellId: cellId("2") });
2534
-
2535
- expect(state.cellIds.getColumns().length).toBe(4);
2536
- expect(formatCells(state)).toMatchInlineSnapshot(`
2537
- "
2538
- > col 0
2539
- [0] ''
2540
-
2541
- > col 1
2542
- [1] ''
2543
-
2544
- > col 2
2545
-
2546
-
2547
- > col 3
2548
- [2] ''
2549
- "
2550
- `);
2551
-
2552
- // Check initial column structure
2553
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([cellId("0")]);
2554
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([cellId("1")]);
2555
- expect(state.cellIds.getColumns()[2].topLevelIds).toEqual([]);
2556
- expect(state.cellIds.getColumns()[3].topLevelIds).toEqual([cellId("2")]);
2557
-
2558
- // Compact columns
2559
- actions.compactColumns();
2560
-
2561
- expect(state.cellIds.getColumns().length).toBe(3);
2562
- expect(formatCells(state)).toMatchInlineSnapshot(`
2563
- "
2564
- > col 0
2565
- [0] ''
2566
-
2567
- > col 1
2568
- [1] ''
2569
-
2570
- > col 2
2571
- [2] ''
2572
- "
2573
- `);
2574
-
2575
- // Check compacted column structure
2576
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([cellId("0")]);
2577
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([cellId("1")]);
2578
- expect(state.cellIds.getColumns()[2].topLevelIds).toEqual([cellId("2")]);
2579
- });
2580
-
2581
- it("rebuildCellColumns regroups cells by config.column", () => {
2582
- // Create four cells in a single column.
2583
- actions.createNewCell({ cellId: firstCellId, before: false });
2584
- actions.createNewCell({ cellId: cellId("1"), before: false });
2585
- actions.createNewCell({ cellId: cellId("2"), before: false });
2586
- expect(state.cellIds.getColumns().length).toBe(1);
2587
-
2588
- // Explicitly set config.column on each cell. This ONLY updates metadata —
2589
- // updateCellConfig does not touch the MultiColumn tree.
2590
- actions.updateCellConfig({
2591
- cellId: cellId("0"),
2592
- config: { column: 0 },
2593
- });
2594
- actions.updateCellConfig({
2595
- cellId: cellId("2"),
2596
- config: { column: 1 },
2597
- });
2598
-
2599
- // Tree is still a single column at this point.
2600
- expect(state.cellIds.getColumns().length).toBe(1);
2601
-
2602
- // Now rebuild the column tree from metadata.
2603
- actions.rebuildCellColumns({
2604
- cellIds: [cellId("0"), cellId("1"), cellId("2"), cellId("3")],
2605
- });
2606
-
2607
- expect(state.cellIds.getColumns().length).toBe(2);
2608
- // Cell 1 inherits from 0 (col 0), cell 3 inherits from 2 (col 1).
2609
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
2610
- cellId("0"),
2611
- cellId("1"),
2612
- ]);
2613
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([
2614
- cellId("2"),
2615
- cellId("3"),
2616
- ]);
2617
- });
2618
-
2619
- it("rebuildCellColumns with explicit column on every cell", () => {
2620
- actions.createNewCell({ cellId: firstCellId, before: false });
2621
- actions.createNewCell({ cellId: cellId("1"), before: false });
2622
- actions.createNewCell({ cellId: cellId("2"), before: false });
2623
-
2624
- for (const [id, col] of [
2625
- ["0", 1],
2626
- ["1", 0],
2627
- ["2", 1],
2628
- ["3", 0],
2629
- ] as const) {
2630
- actions.updateCellConfig({ cellId: cellId(id), config: { column: col } });
2631
- }
2632
-
2633
- actions.rebuildCellColumns({
2634
- cellIds: [cellId("0"), cellId("1"), cellId("2"), cellId("3")],
2635
- });
2636
-
2637
- expect(state.cellIds.getColumns().length).toBe(2);
2638
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
2639
- cellId("1"),
2640
- cellId("3"),
2641
- ]);
2642
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([
2643
- cellId("0"),
2644
- cellId("2"),
2645
- ]);
2646
- });
2647
-
2648
- it("rebuildCellColumns collapses to one column when all cells are col 0", () => {
2649
- actions.createNewCell({ cellId: firstCellId, before: false });
2650
- actions.addColumnBreakpoint({ cellId: cellId("1") });
2651
- expect(state.cellIds.getColumns().length).toBe(2);
2652
-
2653
- // Wipe column metadata back to 0 for both cells.
2654
- actions.updateCellConfig({ cellId: cellId("0"), config: { column: 0 } });
2655
- actions.updateCellConfig({ cellId: cellId("1"), config: { column: 0 } });
2656
-
2657
- actions.rebuildCellColumns({
2658
- cellIds: [cellId("0"), cellId("1")],
2659
- });
2660
-
2661
- expect(state.cellIds.getColumns().length).toBe(1);
2662
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([
2663
- cellId("0"),
2664
- cellId("1"),
2665
- ]);
2666
- });
2667
-
2668
- it("rebuildCellColumns follows the provided order, not current tree order", () => {
2669
- // Start with a single column.
2670
- actions.createNewCell({ cellId: firstCellId, before: false });
2671
- actions.createNewCell({ cellId: cellId("1"), before: false });
2672
- actions.updateCellConfig({ cellId: cellId("0"), config: { column: 0 } });
2673
- actions.updateCellConfig({ cellId: cellId("2"), config: { column: 1 } });
2674
-
2675
- // Pass the reversed order. The rebuild should honor it.
2676
- actions.rebuildCellColumns({
2677
- cellIds: [cellId("2"), cellId("1"), cellId("0")],
2678
- });
2679
-
2680
- // cellIds iteration:
2681
- // 2 → col=1, pushed to col1, prev=1
2682
- // 1 → col=null, pushed to col[prev=1] = col1
2683
- // 0 → col=0, pushed to col0
2684
- expect(state.cellIds.getColumns()[0].topLevelIds).toEqual([cellId("0")]);
2685
- expect(state.cellIds.getColumns()[1].topLevelIds).toEqual([
2686
- cellId("2"),
2687
- cellId("1"),
2688
- ]);
2689
- });
2690
-
2691
926
  it("can clear output of a single cell", () => {
2692
927
  // Set up initial state with output
2693
928
  actions.handleCellMessage({
@@ -2705,7 +940,6 @@ describe("cell reducer", () => {
2705
940
  timestamp: 0,
2706
941
  },
2707
942
  status: "idle",
2708
- stale_inputs: null,
2709
943
  timestamp: new Date(33).getTime() as Seconds,
2710
944
  });
2711
945
 
@@ -2740,7 +974,6 @@ describe("cell reducer", () => {
2740
974
  timestamp: 0,
2741
975
  },
2742
976
  status: "idle",
2743
- stale_inputs: null,
2744
977
  timestamp: new Date(33).getTime() as Seconds,
2745
978
  });
2746
979
 
@@ -2754,7 +987,6 @@ describe("cell reducer", () => {
2754
987
  timestamp: 0,
2755
988
  },
2756
989
  status: "idle",
2757
- stale_inputs: null,
2758
990
  timestamp: new Date(34).getTime() as Seconds,
2759
991
  });
2760
992
 
@@ -2776,8 +1008,8 @@ describe("cell reducer", () => {
2776
1008
 
2777
1009
  it("can clear output of all cells", () => {
2778
1010
  // Create multiple cells with output
2779
- actions.createNewCell({ cellId: firstCellId, before: false });
2780
- const secondCellId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
1011
+ seedCell({ cellId: firstCellId, before: false });
1012
+ const secondCellId = state.cellIds[1];
2781
1013
 
2782
1014
  // Add output to both cells
2783
1015
  const outputMessage = {
@@ -2794,7 +1026,6 @@ describe("cell reducer", () => {
2794
1026
  timestamp: 0,
2795
1027
  },
2796
1028
  status: "idle",
2797
- stale_inputs: null,
2798
1029
  timestamp: new Date(33).getTime() as Seconds,
2799
1030
  } as const;
2800
1031
 
@@ -2802,142 +1033,30 @@ describe("cell reducer", () => {
2802
1033
  ...outputMessage,
2803
1034
  cell_id: firstCellId,
2804
1035
  });
2805
- actions.handleCellMessage({
2806
- ...outputMessage,
2807
- cell_id: secondCellId,
2808
- });
2809
-
2810
- // Verify initial state has output
2811
- expect(state.cellRuntime[firstCellId].output).not.toBeNull();
2812
- expect(state.cellRuntime[firstCellId].consoleOutputs.length).toBe(1);
2813
- expect(state.cellRuntime[secondCellId].output).not.toBeNull();
2814
- expect(state.cellRuntime[secondCellId].consoleOutputs.length).toBe(1);
2815
-
2816
- // Clear all outputs
2817
- actions.clearAllCellOutputs();
2818
-
2819
- // Verify all outputs are cleared
2820
- expect(state.cellRuntime[firstCellId].output).toBeNull();
2821
- expect(state.cellRuntime[firstCellId].consoleOutputs).toEqual([]);
2822
- expect(state.cellRuntime[secondCellId].output).toBeNull();
2823
- expect(state.cellRuntime[secondCellId].consoleOutputs).toEqual([]);
2824
- });
2825
-
2826
- it("skips creating new cell if code exists and skipIfCodeExists is true", () => {
2827
- // Add initial cell with code
2828
- actions.updateCellCode({
2829
- cellId: firstCellId,
2830
- code: "import numpy as np",
2831
- formattingChange: false,
2832
- });
2833
-
2834
- // Try to create new cell with same code and skipIfCodeExists
2835
- actions.createNewCell({
2836
- cellId: "__end__",
2837
- code: "import numpy as np",
2838
- before: false,
2839
- skipIfCodeExists: true,
2840
- });
2841
-
2842
- // Should still only have one cell
2843
- expect(state.cellIds.inOrderIds.length).toBe(1);
2844
- expect(formatCells(state)).toMatchInlineSnapshot(`
2845
- "
2846
- [0] 'import numpy as np'
2847
- "
2848
- `);
2849
-
2850
- // Verify we can still add cell with different code
2851
- actions.createNewCell({
2852
- cellId: "__end__",
2853
- code: "import pandas as pd",
2854
- before: false,
2855
- skipIfCodeExists: true,
2856
- });
2857
-
2858
- expect(state.cellIds.inOrderIds.length).toBe(2);
2859
- expect(formatCells(state)).toMatchInlineSnapshot(`
2860
- "
2861
- [0] 'import numpy as np'
2862
-
2863
- [1] 'import pandas as pd'
2864
- "
2865
- `);
2866
- });
2867
-
2868
- it("can create and noop-update a setup cell", () => {
2869
- // Create the setup cell
2870
- actions.addSetupCellIfDoesntExist({ code: "# Setup code" });
2871
-
2872
- // Check that setup cell was created
2873
- expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
2874
- expect(state.cellData[SETUP_CELL_ID].name).toBe("setup");
2875
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
2876
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2877
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2878
-
2879
- // Update the setup cell
2880
- actions.addSetupCellIfDoesntExist({ code: "# Updated setup code" });
2881
-
2882
- // Check that the setup cell did not change, since it already exists
2883
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
2884
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2885
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2886
- });
2887
-
2888
- it("can delete and undelete the setup cell", () => {
2889
- // Create the setup cell
2890
- actions.addSetupCellIfDoesntExist({ code: "# Setup code" });
2891
-
2892
- // Check that setup cell was created
2893
- expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
2894
- expect(state.cellData[SETUP_CELL_ID].name).toBe("setup");
2895
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
2896
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2897
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2898
-
2899
- // Delete the setup cell
2900
- actions.deleteCell({ cellId: SETUP_CELL_ID });
2901
-
2902
- // Check that setup cell was deleted
2903
- expect(state.cellData[SETUP_CELL_ID]).toBeDefined(); // we keep old state
2904
- expect(state.cellIds.inOrderIds).not.toContain(SETUP_CELL_ID);
2905
-
2906
- // Undo delete the setup cell
2907
- actions.undoDeleteCell();
2908
-
2909
- // Check that setup cell was restored
2910
- expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
2911
- expect(state.cellData[SETUP_CELL_ID].name).toBe("setup");
2912
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
2913
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2914
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2915
-
2916
- // Verify scrollKey is set to the restored setup cell
2917
- expect(state.scrollKey).toBe(SETUP_CELL_ID);
2918
- });
2919
-
2920
- it("can delete and then create a new setup cell", () => {
2921
- // Create the setup cell
2922
- actions.addSetupCellIfDoesntExist({ code: "# Setup code" });
1036
+ actions.handleCellMessage({
1037
+ ...outputMessage,
1038
+ cell_id: secondCellId,
1039
+ });
2923
1040
 
2924
- // Delete the setup cell
2925
- actions.deleteCell({ cellId: SETUP_CELL_ID });
1041
+ // Verify initial state has output
1042
+ expect(state.cellRuntime[firstCellId].output).not.toBeNull();
1043
+ expect(state.cellRuntime[firstCellId].consoleOutputs.length).toBe(1);
1044
+ expect(state.cellRuntime[secondCellId].output).not.toBeNull();
1045
+ expect(state.cellRuntime[secondCellId].consoleOutputs.length).toBe(1);
2926
1046
 
2927
- // Create a new setup cell
2928
- actions.addSetupCellIfDoesntExist({ code: "# New code" });
1047
+ // Clear all outputs
1048
+ actions.clearAllCellOutputs();
2929
1049
 
2930
- // Check that the new setup cell was created
2931
- expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
2932
- expect(state.cellData[SETUP_CELL_ID].name).toBe("setup");
2933
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# New code");
2934
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2935
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
1050
+ // Verify all outputs are cleared
1051
+ expect(state.cellRuntime[firstCellId].output).toBeNull();
1052
+ expect(state.cellRuntime[firstCellId].consoleOutputs).toEqual([]);
1053
+ expect(state.cellRuntime[secondCellId].output).toBeNull();
1054
+ expect(state.cellRuntime[secondCellId].consoleOutputs).toEqual([]);
2936
1055
  });
2937
1056
 
2938
1057
  it("can clear all outputs", () => {
2939
1058
  // Add a cell and give it output
2940
- actions.createNewCell({
1059
+ seedCell({
2941
1060
  cellId: firstCellId,
2942
1061
  before: false,
2943
1062
  });
@@ -2965,522 +1084,13 @@ describe("cell reducer", () => {
2965
1084
  expect(state.cellRuntime[cell2Id].output).toBeNull();
2966
1085
  });
2967
1086
 
2968
- describe("moveToNextCell", () => {
2969
- let cell1Id: CellId;
2970
- let cell2Id: CellId;
2971
- let cell3Id: CellId;
2972
-
2973
- beforeEach(() => {
2974
- // Create a few cells to work with
2975
- actions.createNewCell({ cellId: "__end__", before: false });
2976
- actions.createNewCell({ cellId: "__end__", before: false });
2977
-
2978
- cell1Id = state.cellIds.inOrderIds[0];
2979
- cell2Id = state.cellIds.inOrderIds[1];
2980
- cell3Id = state.cellIds.inOrderIds[2];
2981
- });
2982
-
2983
- it("creates new cell when moving after last cell with noCreate=false", () => {
2984
- const initialCellCount = state.cellIds.inOrderIds.length;
2985
-
2986
- actions.moveToNextCell({
2987
- cellId: cell3Id,
2988
- before: false,
2989
- noCreate: false,
2990
- });
2991
-
2992
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount + 1);
2993
- });
2994
-
2995
- it("creates new cell when moving before first cell with noCreate=false", () => {
2996
- const initialCellCount = state.cellIds.inOrderIds.length;
2997
-
2998
- actions.moveToNextCell({
2999
- cellId: cell1Id,
3000
- before: true,
3001
- noCreate: false,
3002
- });
3003
-
3004
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount + 1);
3005
- });
3006
-
3007
- it("does not create new cell when moving after last cell with noCreate=true", () => {
3008
- const initialCellCount = state.cellIds.inOrderIds.length;
3009
- const initialState = { ...state };
3010
-
3011
- actions.moveToNextCell({
3012
- cellId: cell3Id,
3013
- before: false,
3014
- noCreate: true,
3015
- });
3016
-
3017
- // Should not create a new cell
3018
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount);
3019
- // Should not crash or throw an error
3020
- expect(state.cellIds.inOrderIds).toEqual(initialState.cellIds.inOrderIds);
3021
- });
3022
-
3023
- it("does not create new cell when moving before first cell with noCreate=true", () => {
3024
- const initialCellCount = state.cellIds.inOrderIds.length;
3025
- const initialState = { ...state };
3026
-
3027
- actions.moveToNextCell({ cellId: cell1Id, before: true, noCreate: true });
3028
-
3029
- // Should not create a new cell
3030
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount);
3031
- // Should not crash or throw an error
3032
- expect(state.cellIds.inOrderIds).toEqual(initialState.cellIds.inOrderIds);
3033
- });
3034
-
3035
- it("focuses next cell when moving within bounds", () => {
3036
- const focusSpy = vi.mocked(focusAndScrollCellIntoView);
3037
- focusSpy.mockClear();
3038
-
3039
- actions.moveToNextCell({
3040
- cellId: cell1Id,
3041
- before: false,
3042
- noCreate: true,
3043
- });
3044
-
3045
- expect(focusSpy).toHaveBeenCalledWith({
3046
- cellId: cell2Id,
3047
- cell: state.cellHandles[cell2Id],
3048
- isCodeHidden: false,
3049
- codeFocus: "top",
3050
- variableName: undefined,
3051
- });
3052
- });
3053
-
3054
- it("focuses previous cell when moving backward within bounds", () => {
3055
- const focusSpy = vi.mocked(focusAndScrollCellIntoView);
3056
- focusSpy.mockClear();
3057
-
3058
- actions.moveToNextCell({ cellId: cell2Id, before: true, noCreate: true });
3059
-
3060
- expect(focusSpy).toHaveBeenCalledWith({
3061
- cellId: cell1Id,
3062
- cell: state.cellHandles[cell1Id],
3063
- isCodeHidden: false,
3064
- codeFocus: "bottom",
3065
- variableName: undefined,
3066
- });
3067
- });
3068
-
3069
- it("does not move focus from scratch cell", () => {
3070
- const focusSpy = vi.mocked(focusAndScrollCellIntoView);
3071
- focusSpy.mockClear();
3072
- const initialState = { ...state };
3073
-
3074
- actions.moveToNextCell({
3075
- cellId: SCRATCH_CELL_ID,
3076
- before: false,
3077
- noCreate: false,
3078
- });
3079
-
3080
- expect(focusSpy).not.toHaveBeenCalled();
3081
- expect(state.cellIds.inOrderIds).toEqual(initialState.cellIds.inOrderIds);
3082
- });
3083
- });
3084
-
3085
- describe("untouched cells functionality", () => {
3086
- it("starts with empty untouchedNewCells set", () => {
3087
- expect(state.untouchedNewCells).toEqual(new Set());
3088
- });
3089
-
3090
- it("can create a new cell with hideCode option", () => {
3091
- const initialCellCount = state.cellIds.inOrderIds.length;
3092
-
3093
- actions.createNewCell({
3094
- cellId: "__end__",
3095
- before: false,
3096
- hideCode: true,
3097
- });
3098
-
3099
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount + 1);
3100
- const newCellId =
3101
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3102
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3103
- });
3104
-
3105
- it("adds interactively-created hidden cell with boilerplate code to untouchedNewCells", () => {
3106
- // Markdown cells are created with hideCode and non-empty default code
3107
- // (e.g. `mo.md(r"""\n""")`). They are user-initiated, so their editor
3108
- // should be shown until first blur.
3109
- actions.createNewCell({
3110
- cellId: "__end__",
3111
- before: false,
3112
- code: 'mo.md(r"""\n""")',
3113
- hideCode: true,
3114
- autoFocus: true,
3115
- });
3116
-
3117
- const newCellId =
3118
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3119
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3120
- });
3121
-
3122
- it("does not add programmatically-created hidden cell with code to untouchedNewCells", () => {
3123
- // Cells created by the kernel (e.g. via code_mode) carry code and
3124
- // autoFocus=false; their hide_code must take effect immediately.
3125
- actions.createNewCell({
3126
- cellId: "__end__",
3127
- before: false,
3128
- code: "x = 1",
3129
- hideCode: true,
3130
- autoFocus: false,
3131
- });
3132
-
3133
- const newCellId =
3134
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3135
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3136
- });
3137
-
3138
- it("does not add cell to untouchedNewCells when hideCode is false", () => {
3139
- actions.createNewCell({
3140
- cellId: "__end__",
3141
- before: false,
3142
- hideCode: false,
3143
- });
3144
-
3145
- const newCellId =
3146
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3147
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3148
- });
3149
-
3150
- it("does not add cell to untouchedNewCells when hideCode is undefined", () => {
3151
- actions.createNewCell({
3152
- cellId: "__end__",
3153
- before: false,
3154
- });
3155
-
3156
- const newCellId =
3157
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3158
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3159
- });
3160
-
3161
- it("can mark a cell as touched", () => {
3162
- // Create a cell with hideCode
3163
- actions.createNewCell({
3164
- cellId: "__end__",
3165
- before: false,
3166
- hideCode: 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
- // Mark it as touched
3174
- actions.markTouched({ cellId: newCellId });
3175
-
3176
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3177
- });
3178
-
3179
- it("markTouched is idempotent", () => {
3180
- // Create a cell with hideCode
3181
- actions.createNewCell({
3182
- cellId: "__end__",
3183
- before: false,
3184
- hideCode: true,
3185
- });
3186
-
3187
- const newCellId =
3188
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3189
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3190
-
3191
- // Mark it as touched multiple times
3192
- actions.markTouched({ cellId: newCellId });
3193
- actions.markTouched({ cellId: newCellId });
3194
- actions.markTouched({ cellId: newCellId });
3195
-
3196
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3197
- });
3198
-
3199
- it("can mark a non-existent cell as touched without error", () => {
3200
- const nonExistentCellId = cellId("non-existent");
3201
-
3202
- expect(() => {
3203
- actions.markTouched({ cellId: nonExistentCellId });
3204
- }).not.toThrow();
3205
-
3206
- expect(state.untouchedNewCells.has(nonExistentCellId)).toBe(false);
3207
- });
3208
-
3209
- it("can handle multiple untouched cells", () => {
3210
- // Create multiple cells with hideCode
3211
- actions.createNewCell({
3212
- cellId: "__end__",
3213
- before: false,
3214
- hideCode: true,
3215
- });
3216
- actions.createNewCell({
3217
- cellId: "__end__",
3218
- before: false,
3219
- hideCode: true,
3220
- });
3221
- actions.createNewCell({
3222
- cellId: "__end__",
3223
- before: false,
3224
- hideCode: false,
3225
- });
3226
-
3227
- const cellIds = state.cellIds.inOrderIds;
3228
- const cell1Id = cellIds[cellIds.length - 3];
3229
- const cell2Id = cellIds[cellIds.length - 2];
3230
- const cell3Id = cellIds[cellIds.length - 1];
3231
-
3232
- expect(state.untouchedNewCells.has(cell1Id)).toBe(true);
3233
- expect(state.untouchedNewCells.has(cell2Id)).toBe(true);
3234
- expect(state.untouchedNewCells.has(cell3Id)).toBe(false);
3235
-
3236
- // Mark one as touched
3237
- actions.markTouched({ cellId: cell1Id });
3238
-
3239
- expect(state.untouchedNewCells.has(cell1Id)).toBe(false);
3240
- expect(state.untouchedNewCells.has(cell2Id)).toBe(true);
3241
- expect(state.untouchedNewCells.has(cell3Id)).toBe(false);
3242
- });
3243
-
3244
- it("preserves untouched state when cells are moved", () => {
3245
- // Create cells with hideCode
3246
- actions.createNewCell({
3247
- cellId: "__end__",
3248
- before: false,
3249
- hideCode: true,
3250
- });
3251
- actions.createNewCell({
3252
- cellId: "__end__",
3253
- before: false,
3254
- hideCode: true,
3255
- });
3256
-
3257
- const cellIds = state.cellIds.inOrderIds;
3258
- const cell1Id = cellIds[cellIds.length - 2];
3259
- const cell2Id = cellIds[cellIds.length - 1];
3260
-
3261
- expect(state.untouchedNewCells.has(cell1Id)).toBe(true);
3262
- expect(state.untouchedNewCells.has(cell2Id)).toBe(true);
3263
-
3264
- // Move cell1 to the end
3265
- actions.moveCell({
3266
- cellId: cell1Id,
3267
- before: false,
3268
- });
3269
-
3270
- // Both cells should still be untouched
3271
- expect(state.untouchedNewCells.has(cell1Id)).toBe(true);
3272
- expect(state.untouchedNewCells.has(cell2Id)).toBe(true);
3273
- });
3274
-
3275
- it("does not remove untouched state when cell is deleted", () => {
3276
- // We could implement this, but it doesn't actually affect downstream behavior.
3277
- // It is easier to leave it, so `undo` works as expected.
3278
-
3279
- // Create a cell with hideCode
3280
- actions.createNewCell({
3281
- cellId: "__end__",
3282
- before: false,
3283
- hideCode: true,
3284
- });
3285
-
3286
- const newCellId =
3287
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3288
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3289
-
3290
- // Delete the cell
3291
- actions.deleteCell({ cellId: newCellId });
3292
-
3293
- // Does not actually remove untouched state
3294
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3295
- });
3296
-
3297
- it("restores untouched state when cell deletion is undone", () => {
3298
- // Create a cell with hideCode
3299
- actions.createNewCell({
3300
- cellId: "__end__",
3301
- before: false,
3302
- hideCode: true,
3303
- });
3304
-
3305
- const newCellId =
3306
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3307
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3308
-
3309
- // Delete the cell
3310
- actions.deleteCell({ cellId: newCellId });
3311
- // Still exists in untouchedNewCells
3312
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3313
-
3314
- // Undo the deletion
3315
- actions.undoDeleteCell();
3316
-
3317
- // The cell should be restored but no longer untouched
3318
- // (this is expected behavior - undoing doesn't restore untouched state)
3319
- const restoredCellId =
3320
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3321
- expect(state.untouchedNewCells.has(restoredCellId)).toBe(false);
3322
- });
3323
-
3324
- it("untouched cell behavior with hide_code config", () => {
3325
- // Create a cell with hideCode and hide_code config
3326
- actions.createNewCell({
3327
- cellId: "__end__",
3328
- before: false,
3329
- hideCode: true,
3330
- });
3331
-
3332
- const newCellId =
3333
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3334
-
3335
- // Update the cell config to hide code
3336
- actions.updateCellConfig({
3337
- cellId: newCellId,
3338
- config: { hide_code: true },
3339
- });
3340
-
3341
- // Cell should be untouched and have hide_code config
3342
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3343
- expect(state.cellData[newCellId].config.hide_code).toBe(true);
3344
-
3345
- // Code should not be hidden because cell is untouched
3346
- expect(exportedForTesting.isCellCodeHidden(state, newCellId)).toBe(false);
3347
-
3348
- // Mark as touched
3349
- actions.markTouched({ cellId: newCellId });
3350
-
3351
- // Now code should be hidden
3352
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3353
- expect(exportedForTesting.isCellCodeHidden(state, newCellId)).toBe(true);
3354
- });
3355
-
3356
- it("can mark an existing cell as untouched", () => {
3357
- // Create a cell without hideCode (not in untouchedNewCells)
3358
- actions.createNewCell({
3359
- cellId: "__end__",
3360
- before: false,
3361
- hideCode: false,
3362
- });
3363
-
3364
- const newCellId =
3365
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3366
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3367
-
3368
- // Mark it as untouched
3369
- actions.markUntouched({ cellId: newCellId });
3370
-
3371
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3372
- });
3373
-
3374
- it("markUntouched is idempotent", () => {
3375
- // Create a cell without hideCode
3376
- actions.createNewCell({
3377
- cellId: "__end__",
3378
- before: false,
3379
- hideCode: false,
3380
- });
3381
-
3382
- const newCellId =
3383
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3384
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3385
-
3386
- // Mark as untouched multiple times
3387
- actions.markUntouched({ cellId: newCellId });
3388
- actions.markUntouched({ cellId: newCellId });
3389
- actions.markUntouched({ cellId: newCellId });
3390
-
3391
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3392
- });
3393
-
3394
- it("markUntouched does not affect already untouched cells", () => {
3395
- // Create a cell with hideCode (already in untouchedNewCells)
3396
- actions.createNewCell({
3397
- cellId: "__end__",
3398
- before: false,
3399
- hideCode: true,
3400
- });
3401
-
3402
- const newCellId =
3403
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3404
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3405
-
3406
- // Calling markUntouched should not change anything
3407
- actions.markUntouched({ cellId: newCellId });
3408
-
3409
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3410
- });
3411
-
3412
- it("markTouched and markUntouched can toggle cell state", () => {
3413
- // Create a cell without hideCode
3414
- actions.createNewCell({
3415
- cellId: "__end__",
3416
- before: false,
3417
- hideCode: false,
3418
- });
3419
-
3420
- const newCellId =
3421
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3422
-
3423
- // Initially not untouched
3424
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3425
-
3426
- // Mark as untouched
3427
- actions.markUntouched({ cellId: newCellId });
3428
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3429
-
3430
- // Mark as touched
3431
- actions.markTouched({ cellId: newCellId });
3432
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
3433
-
3434
- // Mark as untouched again
3435
- actions.markUntouched({ cellId: newCellId });
3436
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3437
- });
3438
-
3439
- it("markUntouched works for markdown cell conversion scenario", () => {
3440
- // Simulates converting a Python cell to Markdown
3441
- // 1. Create a regular cell (no hideCode)
3442
- actions.createNewCell({
3443
- cellId: "__end__",
3444
- before: false,
3445
- hideCode: false,
3446
- });
3447
-
3448
- const cellId =
3449
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3450
-
3451
- // Cell starts without hide_code and not in untouchedNewCells
3452
- expect(state.cellData[cellId].config.hide_code).toBe(false);
3453
- expect(state.untouchedNewCells.has(cellId)).toBe(false);
3454
- expect(exportedForTesting.isCellCodeHidden(state, cellId)).toBe(false);
3455
-
3456
- // 2. Convert to markdown: set hide_code and mark as untouched
3457
- actions.updateCellConfig({
3458
- cellId,
3459
- config: { hide_code: true },
3460
- });
3461
- actions.markUntouched({ cellId });
3462
-
3463
- // Code should NOT be hidden because cell is untouched (user can edit)
3464
- expect(state.cellData[cellId].config.hide_code).toBe(true);
3465
- expect(state.untouchedNewCells.has(cellId)).toBe(true);
3466
- expect(exportedForTesting.isCellCodeHidden(state, cellId)).toBe(false);
3467
-
3468
- // 3. User blurs the cell (markTouched)
3469
- actions.markTouched({ cellId });
3470
-
3471
- // Now code should be hidden
3472
- expect(state.untouchedNewCells.has(cellId)).toBe(false);
3473
- expect(exportedForTesting.isCellCodeHidden(state, cellId)).toBe(true);
3474
- });
3475
- });
3476
-
3477
1087
  describe("releaseCellAtoms", () => {
3478
1088
  it("atom families cache atoms until explicitly removed", () => {
3479
1089
  const { cellDataAtom, cellRuntimeAtom, cellHandleAtom } =
3480
1090
  exportedForTesting;
3481
1091
 
3482
- actions.createNewCell({ cellId: firstCellId, before: false });
3483
- const newCellId = state.cellIds.inOrderIds[1];
1092
+ seedCell({ cellId: firstCellId, before: false });
1093
+ const newCellId = state.cellIds[1];
3484
1094
 
3485
1095
  const dataAtom1 = cellDataAtom(newCellId);
3486
1096
  const runtimeAtom1 = cellRuntimeAtom(newCellId);
@@ -3495,116 +1105,15 @@ describe("cell reducer", () => {
3495
1105
  expect(runtimeAtom2).toBe(runtimeAtom1);
3496
1106
  expect(handleAtom2).toBe(handleAtom1);
3497
1107
  });
3498
-
3499
- it("cleans up atom family cache when cells are deleted", () => {
3500
- const { cellDataAtom, cellRuntimeAtom, cellHandleAtom } =
3501
- exportedForTesting;
3502
-
3503
- actions.createNewCell({ cellId: firstCellId, before: false });
3504
- const newCellId = state.cellIds.inOrderIds[1];
3505
-
3506
- // Access to create cache entries
3507
- const dataAtom1 = cellDataAtom(newCellId);
3508
- const runtimeAtom1 = cellRuntimeAtom(newCellId);
3509
- const handleAtom1 = cellHandleAtom(newCellId);
3510
-
3511
- // Triggers purge
3512
- actions.deleteCell({ cellId: newCellId });
3513
-
3514
- // Access again (should be new instances after cleanup)
3515
- const dataAtom2 = cellDataAtom(newCellId);
3516
- const runtimeAtom2 = cellRuntimeAtom(newCellId);
3517
- const handleAtom2 = cellHandleAtom(newCellId);
3518
-
3519
- expect(dataAtom2).not.toBe(dataAtom1);
3520
- expect(runtimeAtom2).not.toBe(runtimeAtom1);
3521
- expect(handleAtom2).not.toBe(handleAtom1);
3522
- });
3523
- });
3524
- });
3525
-
3526
- describe("isCellCodeHidden", () => {
3527
- const state = initialNotebookState();
3528
- const firstCellId = state.cellIds.inOrderIds[0];
3529
-
3530
- it("returns false when hide_code is false and cell is not untouched", () => {
3531
- const testCellId = cellId("test-cell");
3532
- const testState: NotebookState = {
3533
- ...state,
3534
- cellData: {
3535
- ...state.cellData,
3536
- [testCellId]: {
3537
- ...state.cellData[firstCellId],
3538
- id: testCellId,
3539
- config: { hide_code: false, disabled: false, column: null },
3540
- },
3541
- },
3542
- untouchedNewCells: new Set(),
3543
- };
3544
-
3545
- expect(exportedForTesting.isCellCodeHidden(testState, testCellId)).toBe(
3546
- false,
3547
- );
3548
- });
3549
-
3550
- it("returns true when hide_code is true and cell is not untouched", () => {
3551
- const testCellId = cellId("test-cell");
3552
- const testState: NotebookState = {
3553
- ...state,
3554
- cellData: {
3555
- ...state.cellData,
3556
- [testCellId]: {
3557
- ...state.cellData[firstCellId],
3558
- id: testCellId,
3559
- config: { hide_code: true, disabled: false, column: null },
3560
- },
3561
- },
3562
- untouchedNewCells: new Set(),
3563
- };
3564
-
3565
- expect(exportedForTesting.isCellCodeHidden(testState, testCellId)).toBe(
3566
- true,
3567
- );
3568
- });
3569
-
3570
- it("returns false when hide_code is true but cell is untouched", () => {
3571
- const testCellId = cellId("test-cell");
3572
- const testState: NotebookState = {
3573
- ...state,
3574
- cellData: {
3575
- ...state.cellData,
3576
- [testCellId]: {
3577
- ...state.cellData[firstCellId],
3578
- id: testCellId,
3579
- config: { hide_code: true, disabled: false, column: null },
3580
- },
3581
- },
3582
- untouchedNewCells: new Set([testCellId]),
3583
- };
3584
-
3585
- expect(exportedForTesting.isCellCodeHidden(testState, testCellId)).toBe(
3586
- false,
3587
- );
3588
1108
  });
1109
+ it("withdraws the submission of a cell that leaves the document", () => {
1110
+ MockExecutions.seed(store, []);
1111
+ submitCells(store, [firstCellId]);
1112
+ expect(store.get(notebookQueuedOrRunningCountAtom)).toBe(1);
3589
1113
 
3590
- it("returns false when hide_code is false and cell is untouched", () => {
3591
- const testCellId = cellId("test-cell");
3592
- const testState: NotebookState = {
3593
- ...state,
3594
- cellData: {
3595
- ...state.cellData,
3596
- [testCellId]: {
3597
- ...state.cellData[firstCellId],
3598
- id: testCellId,
3599
- config: { hide_code: false, disabled: false, column: null },
3600
- },
3601
- },
3602
- untouchedNewCells: new Set([testCellId]),
3603
- };
1114
+ actions.reconcileDocument({ order: [], cells: new Map() });
3604
1115
 
3605
- expect(exportedForTesting.isCellCodeHidden(testState, testCellId)).toBe(
3606
- false,
3607
- );
1116
+ expect(store.get(notebookQueuedOrRunningCountAtom)).toBe(0);
3608
1117
  });
3609
1118
  });
3610
1119
 
@@ -3787,11 +1296,11 @@ describe("createTracebackInfoAtom", () => {
3787
1296
  data: [{ type: "syntax", msg: "Syntax error", lineno: 1 }],
3788
1297
  mimetype: "application/vnd.marimo+error",
3789
1298
  },
3790
- status: "queued",
3791
1299
  },
3792
1300
  },
3793
1301
  }),
3794
1302
  );
1303
+ MockExecutions.seed(store, [MockExecutions.queued(cellId("cell1"))]);
3795
1304
 
3796
1305
  const tracebackAtom = exportedForTesting.createTracebackInfoAtom(
3797
1306
  cellId("cell1"),
@@ -3819,11 +1328,11 @@ describe("createTracebackInfoAtom", () => {
3819
1328
  data: [{ type: "syntax", msg: "Syntax error", lineno: 1 }],
3820
1329
  mimetype: "application/vnd.marimo+error",
3821
1330
  },
3822
- status: "running",
3823
1331
  },
3824
1332
  },
3825
1333
  }),
3826
1334
  );
1335
+ MockExecutions.seed(store, [MockExecutions.running(cellId("cell1"))]);
3827
1336
 
3828
1337
  const tracebackAtom = exportedForTesting.createTracebackInfoAtom(
3829
1338
  cellId("cell1"),
@@ -3834,31 +1343,9 @@ describe("createTracebackInfoAtom", () => {
3834
1343
  });
3835
1344
  });
3836
1345
 
3837
- describe("setCells snapshot preservation", () => {
1346
+ describe("kernel-ready", () => {
3838
1347
  const CELL_A = cellId("A");
3839
1348
  const CELL_B = cellId("B");
3840
- const newCells: CellData[] = [
3841
- {
3842
- id: CELL_A,
3843
- name: "a",
3844
- code: "1",
3845
- edited: false,
3846
- lastCodeRun: null,
3847
- lastExecutionTime: null,
3848
- config: { hide_code: false, disabled: false, column: null },
3849
- serializedEditorState: null,
3850
- },
3851
- {
3852
- id: CELL_B,
3853
- name: "b",
3854
- code: "2",
3855
- edited: false,
3856
- lastCodeRun: null,
3857
- lastExecutionTime: null,
3858
- config: { hide_code: false, disabled: false, column: null },
3859
- serializedEditorState: null,
3860
- },
3861
- ];
3862
1349
 
3863
1350
  const hydratedState = () =>
3864
1351
  MockNotebook.notebookState({
@@ -3888,44 +1375,74 @@ describe("setCells snapshot preservation", () => {
3888
1375
  },
3889
1376
  });
3890
1377
 
3891
- beforeEach(async () => {
3892
- const { isWasm } = await import("@/core/wasm/utils");
3893
- vi.mocked(isWasm).mockReturnValue(true);
3894
- });
3895
-
3896
- it("preserves hydrated output in WASM", () => {
1378
+ it("resetCellRuntimes starts every cell's runtime over", () => {
3897
1379
  const next = exportedForTesting.reducer(hydratedState(), {
3898
- type: "setCells",
3899
- payload: newCells,
1380
+ type: "resetCellRuntimes",
1381
+ payload: undefined,
3900
1382
  });
3901
1383
 
3902
- expect(next.cellRuntime[CELL_A].output).toMatchObject({
3903
- data: "hydrated-A",
1384
+ expect(next.cellRuntime[CELL_A].output).toBeNull();
1385
+ expect(next.cellRuntime[CELL_B].consoleOutputs).toEqual([]);
1386
+ });
1387
+
1388
+ it("resetCellRuntimes starts the scratch cell over too", () => {
1389
+ const state = MockNotebook.notebookState({
1390
+ cellData: { [CELL_A]: { id: CELL_A, code: "1" } },
1391
+ cellRuntime: {
1392
+ [SCRATCH_CELL_ID]: {
1393
+ output: {
1394
+ channel: "output",
1395
+ mimetype: "text/plain",
1396
+ data: "scratch",
1397
+ timestamp: 0,
1398
+ },
1399
+ },
1400
+ },
3904
1401
  });
1402
+ const next = exportedForTesting.reducer(state, {
1403
+ type: "resetCellRuntimes",
1404
+ payload: undefined,
1405
+ });
1406
+
1407
+ expect(next.cellRuntime[SCRATCH_CELL_ID].output).toBeNull();
3905
1408
  });
3906
1409
 
3907
- it("preserves console-only hydration in WASM", () => {
3908
- const next = exportedForTesting.reducer(hydratedState(), {
3909
- type: "setCells",
3910
- payload: newCells,
1410
+ describe("resetCellRuntimes in WASM", () => {
1411
+ beforeEach(async () => {
1412
+ const { isWasm } = await import("@/core/wasm/utils");
1413
+ vi.mocked(isWasm).mockReturnValue(true);
3911
1414
  });
3912
1415
 
3913
- expect(next.cellRuntime[CELL_B].consoleOutputs).toHaveLength(1);
3914
- expect(next.cellRuntime[CELL_B].consoleOutputs[0]).toMatchObject({
3915
- data: "hydrated-B-stdout",
1416
+ afterEach(async () => {
1417
+ const { isWasm } = await import("@/core/wasm/utils");
1418
+ vi.mocked(isWasm).mockReturnValue(false);
3916
1419
  });
3917
- });
3918
1420
 
3919
- it("resets cells with no prior runtime even in WASM", () => {
3920
- const empty = MockNotebook.notebookState({ cellData: {} });
3921
- const next = exportedForTesting.reducer(empty, {
3922
- type: "setCells",
3923
- payload: newCells,
1421
+ it("keeps hydrated outputs and console", () => {
1422
+ const next = exportedForTesting.reducer(hydratedState(), {
1423
+ type: "resetCellRuntimes",
1424
+ payload: undefined,
1425
+ });
1426
+
1427
+ expect(next.cellRuntime[CELL_A].output).toMatchObject({
1428
+ data: "hydrated-A",
1429
+ });
1430
+ expect(next.cellRuntime[CELL_B].consoleOutputs[0]).toMatchObject({
1431
+ data: "hydrated-B-stdout",
1432
+ });
3924
1433
  });
3925
1434
 
3926
- expect(next.cellRuntime[CELL_A].output).toBeNull();
3927
- expect(next.cellRuntime[CELL_A].consoleOutputs).toEqual([]);
3928
- expect(next.cellRuntime[CELL_B].output).toBeNull();
3929
- expect(next.cellRuntime[CELL_B].consoleOutputs).toEqual([]);
1435
+ it("starts cells with no prior runtime over", () => {
1436
+ const state = MockNotebook.notebookState({
1437
+ cellData: { [CELL_A]: { id: CELL_A, code: "1" } },
1438
+ });
1439
+ const next = exportedForTesting.reducer(state, {
1440
+ type: "resetCellRuntimes",
1441
+ payload: undefined,
1442
+ });
1443
+
1444
+ expect(next.cellRuntime[CELL_A].output).toBeNull();
1445
+ expect(next.cellRuntime[CELL_A].consoleOutputs).toEqual([]);
1446
+ });
3930
1447
  });
3931
1448
  });