@marimo-team/islands 0.23.7-dev4 → 0.23.7-dev42

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 (513) hide show
  1. package/dist/{ConnectedDataExplorerComponent-PmilQqXR.js → ConnectedDataExplorerComponent-2lBNiUv6.js} +14 -14
  2. package/dist/{ErrorBoundary-Da4UeYxT.js → ErrorBoundary-D3wrPNma.js} +1 -1
  3. package/dist/{any-language-editor-BVR0l12r.js → any-language-editor-VWs_7v27.js} +15 -15
  4. package/dist/apl-Bdc61P1y.js +4 -0
  5. package/dist/{arc-CHF8PiiF.js → arc-DfkSnvZm.js} +2 -2
  6. package/dist/{architecture-7HQA4BMR-D0JB_3hE.js → architecture-7HQA4BMR-CS9jOrqM.js} +1 -1
  7. package/dist/{architectureDiagram-VXUJARFQ-BXQEUDtK.js → architectureDiagram-VXUJARFQ-CXVJxFhH.js} +16 -16
  8. package/dist/asciiarmor-DVRHDGzT.js +4 -0
  9. package/dist/asn1-BmuKfkfu.js +4 -0
  10. package/dist/assets/__vite-browser-external-CAdMKBac.js +1 -0
  11. package/dist/assets/worker-CpBbwbQo.js +73 -0
  12. package/dist/{blockDiagram-VD42YOAC-DhJe-Y9i.js → blockDiagram-VD42YOAC-DGDaxR8I.js} +11 -11
  13. package/dist/brainfuck-DPrRpyTV.js +4 -0
  14. package/dist/{button-CA5pI2YF.js → button-Dj4BTre0.js} +5 -0
  15. package/dist/{c4Diagram-YG6GDRKO-9dSfzOFR.js → c4Diagram-YG6GDRKO-C2hc6ne8.js} +5 -5
  16. package/dist/{capabilities-6laDasij.js → capabilities-C9rrYCzf.js} +1 -1
  17. package/dist/{channel-MqYIiKgS.js → channel-BBoIVUrJ.js} +1 -1
  18. package/dist/{chat-ui-B-gbqk_F.js → chat-ui-CufH8sfF.js} +626 -234
  19. package/dist/{check-CFM2mVDr.js → check-BcUIXnUT.js} +1 -1
  20. package/dist/{chunk-4BX2VUAB-BwfrWBqN.js → chunk-4BX2VUAB-CzXltWHN.js} +1 -1
  21. package/dist/{chunk-55IACEB6-D8THf2mi.js → chunk-55IACEB6-B-1mjMMC.js} +1 -1
  22. package/dist/{chunk-5FQGJX7Z-CO1e63h_.js → chunk-5FQGJX7Z-BOg95xG5.js} +2 -2
  23. package/dist/{chunk-ABZYJK2D-BrBb_0yY.js → chunk-ABZYJK2D-D0cLy8Bb.js} +2 -2
  24. package/dist/{chunk-ATLVNIR6-D-0XqNah.js → chunk-ATLVNIR6-BXsEjlHF.js} +2 -2
  25. package/dist/{chunk-B4BG7PRW-8iRKvugR.js → chunk-B4BG7PRW-Q1usn6T3.js} +7 -7
  26. package/dist/{chunk-CVBHYZKI-B6xhgaBd.js → chunk-CVBHYZKI-B_c5YBcW.js} +1 -1
  27. package/dist/{chunk-DI55MBZ5-C0_2D4m4.js → chunk-DI55MBZ5-D1qLYNrb.js} +6 -6
  28. package/dist/{chunk-EXTU4WIE-Jiw9ca1u.js → chunk-EXTU4WIE-BKNXdLmD.js} +2 -2
  29. package/dist/{chunk-FMBD7UC4-CHdus51S.js → chunk-FMBD7UC4-Ie8M9q0W.js} +1 -1
  30. package/dist/{chunk-HN2XXSSU-2Vfbq-kU.js → chunk-HN2XXSSU-E3n-Ys7Z.js} +1 -1
  31. package/dist/{chunk-JA3XYJ7Z-6wbaigKe.js → chunk-JA3XYJ7Z-D6c6cOBG.js} +3 -3
  32. package/dist/{chunk-JZLCHNYA-_rfptlUP.js → chunk-JZLCHNYA-BvsPHJmL.js} +6 -6
  33. package/dist/{chunk-MI3HLSF2-Do0-KRc0.js → chunk-MI3HLSF2-CUYEasXO.js} +1 -1
  34. package/dist/{chunk-N4CR4FBY-DIZG9dVD.js → chunk-N4CR4FBY-8ycT-O9a.js} +7 -7
  35. package/dist/{chunk-QN33PNHL-Cc64y40m.js → chunk-QN33PNHL-Bb-eUBW3.js} +2 -2
  36. package/dist/{chunk-QXUST7PY-BDG0-0Or.js → chunk-QXUST7PY-DV8yRwBd.js} +10 -10
  37. package/dist/{chunk-QZHKN3VN-B_Mdb8GC.js → chunk-QZHKN3VN-DRjXVwuJ.js} +1 -1
  38. package/dist/{chunk-S3R3BYOJ-DphMP0FA.js → chunk-S3R3BYOJ-mQeCz5CE.js} +4 -4
  39. package/dist/{chunk-TZMSLE5B-C9LUoYkc.js → chunk-TZMSLE5B-BqW10dHe.js} +3 -3
  40. package/dist/classDiagram-2ON5EDUG--Yh__LHb.js +30 -0
  41. package/dist/classDiagram-v2-WZHVMYZB-BC7X7Xtc.js +30 -0
  42. package/dist/{clike-sBZrGeF8.js → clike-DTxNUn7l.js} +1 -1
  43. package/dist/clojure-DEttQW5T.js +4 -0
  44. package/dist/cmake-jNlx_DaM.js +4 -0
  45. package/dist/cobol-BvvIm5MJ.js +4 -0
  46. package/dist/{code-block-37QAKDTI-0JNwiPGv.js → code-block-37QAKDTI-BsGy1AOJ.js} +1 -1
  47. package/dist/{code-visibility-zYxaYsuj.js → code-visibility-CTdccsPx.js} +573 -585
  48. package/dist/coffeescript-DTpBMyFU.js +4 -0
  49. package/dist/commonlisp-BmmUG8jb.js +4 -0
  50. package/dist/{copy-TGGAUEWp.js → copy-DLf4aN7I.js} +2 -2
  51. package/dist/{cose-bilkent-S5V4N54A-DXHZkJKX.js → cose-bilkent-S5V4N54A-kjoZoid4.js} +2 -2
  52. package/dist/crystal-BxyqmEWC.js +4 -0
  53. package/dist/css-BGoCtuG3.js +4 -0
  54. package/dist/cypher-BmCbdl3u.js +4 -0
  55. package/dist/d-CFrlbjZt.js +4 -0
  56. package/dist/{dagre-6UL2VRFP-tH87fkPA.js → dagre-6UL2VRFP-DRBWoQUw.js} +10 -10
  57. package/dist/{data-grid-overlay-editor-CWUN78-s.js → data-grid-overlay-editor-efe5ZagF.js} +2 -2
  58. package/dist/{diagram-PSM6KHXK-2VjPSCDn.js → diagram-PSM6KHXK-H66ATWP2.js} +18 -18
  59. package/dist/{diagram-QEK2KX5R-CiLmNyta.js → diagram-QEK2KX5R-DItl5Wns.js} +14 -14
  60. package/dist/{diagram-S2PKOQOG-Zha_1CLx.js → diagram-S2PKOQOG-CtuW_ZuL.js} +14 -14
  61. package/dist/diff-D6XwL6P8.js +4 -0
  62. package/dist/{dist-Brkazupz.js → dist--sWVZwjW.js} +1 -1
  63. package/dist/{dist-FN0ZA_8F.js → dist-21ButRCu.js} +1 -1
  64. package/dist/{dist-BetEKbPG.js → dist-B8RaFTRF.js} +1 -1
  65. package/dist/dist-BoHGySTM.js +5 -0
  66. package/dist/dist-ByAz19Qc.js +5 -0
  67. package/dist/dist-C93EysN4.js +5 -0
  68. package/dist/{dist-BHnX0ia_.js → dist-CY-lVor6.js} +1 -1
  69. package/dist/{dist-YP-G7W0f.js → dist-CYDuv4bR.js} +1 -1
  70. package/dist/{dist-CMjD5MQb.js → dist-Cfo5EE2t.js} +1 -1
  71. package/dist/dist-CjivSDvN.js +5 -0
  72. package/dist/dist-Cqwx-MH7.js +5 -0
  73. package/dist/{dist-ESg7xyoD.js → dist-D3ZI9nhS.js} +2 -2
  74. package/dist/{dist-DkC6YEo0.js → dist-DMZNjfX4.js} +1 -1
  75. package/dist/{dist-ChC1BhqM.js → dist-DbpcoFAV.js} +1 -1
  76. package/dist/dist-FUNenbiQ.js +5 -0
  77. package/dist/{dist-BEOU2g1b.js → dist-zhSud5X3.js} +1 -1
  78. package/dist/{dockerfile-COvlVLcE.js → dockerfile-twL37N91.js} +1 -1
  79. package/dist/dtd-Bw0lRN0-.js +4 -0
  80. package/dist/dylan-B55eBHTt.js +4 -0
  81. package/dist/ecl-C8G4p0wn.js +4 -0
  82. package/dist/eiffel-BmH46VJl.js +4 -0
  83. package/dist/elm-DhzeFqkl.js +4 -0
  84. package/dist/{erDiagram-Q2GNP2WA-biHZS05w.js → erDiagram-Q2GNP2WA--19X2kU5.js} +14 -14
  85. package/dist/erlang-CY-wdlsU.js +4 -0
  86. package/dist/{error-banner-DnBPzEWg.js → error-banner-CVkfBUT3.js} +2 -2
  87. package/dist/{esm-Dd1z1auZ.js → esm-CWp0KQeK.js} +1 -1
  88. package/dist/{esm-CYEyrE3Y.js → esm-DjNnlmpf.js} +96 -96
  89. package/dist/{extends-CzJgxo2J.js → extends-vAi97cpa.js} +4 -4
  90. package/dist/{factor-C2GT7jfQ.js → factor-CajWS6mS.js} +1 -1
  91. package/dist/factor-DWkgl0xw.js +4 -0
  92. package/dist/{flowDiagram-NV44I4VS-CWWlUpBR.js → flowDiagram-NV44I4VS-DQmWlo7f.js} +16 -16
  93. package/dist/{formats-CgaK7Gmx.js → formats-CpgZM9BM.js} +3 -3
  94. package/dist/forth-Cij_ie2t.js +4 -0
  95. package/dist/fortran-Br3X9cfm.js +4 -0
  96. package/dist/{ganttDiagram-JELNMOA3-D7B2c4Z9.js → ganttDiagram-JELNMOA3-BOGXJ8Lk.js} +7 -7
  97. package/dist/gas-DYsGcMN2.js +4 -0
  98. package/dist/gherkin-eVgXQ0fQ.js +4 -0
  99. package/dist/{gitGraph-G5XIXVHT-BdepdFa_.js → gitGraph-G5XIXVHT-DGlbae5m.js} +1 -1
  100. package/dist/{gitGraphDiagram-V2S2FVAM-CtLvNR1S.js → gitGraphDiagram-V2S2FVAM-DjzxfF0P.js} +14 -14
  101. package/dist/{glide-data-editor-CvlvtPWJ.js → glide-data-editor-BK9s_dqy.js} +13 -13
  102. package/dist/groovy-_NFHIXG7.js +4 -0
  103. package/dist/haskell-D6xNG_bH.js +4 -0
  104. package/dist/haxe-sU_rzAwn.js +5 -0
  105. package/dist/{html-to-image-hMMPiNe_.js → html-to-image-DxWM1HVj.js} +2395 -2301
  106. package/dist/idl-Ds_VbrUx.js +4 -0
  107. package/dist/{info-VBDWY6EO--JNA2rNu.js → info-VBDWY6EO-D2lvLLw5.js} +1 -1
  108. package/dist/{infoDiagram-HS3SLOUP-BbZyOxsP.js → infoDiagram-HS3SLOUP-ChNufFsP.js} +12 -12
  109. package/dist/{input-BAOe64zx.js → input-Cc1Vvw9A.js} +6 -6
  110. package/dist/javascript-CztfIl0i.js +4 -0
  111. package/dist/{journeyDiagram-XKPGCS4Q-BU2mjjzl.js → journeyDiagram-XKPGCS4Q-BO_O4Ij1.js} +6 -6
  112. package/dist/julia-DDv40QMV.js +4 -0
  113. package/dist/{kanban-definition-3W4ZIXB7-BlmczUuw.js → kanban-definition-3W4ZIXB7-CPpiiiWk.js} +11 -11
  114. package/dist/{katex-qPqrBHZ8.js → katex-9-9QRhxz.js} +1 -1
  115. package/dist/{label-BCWi-Oqu.js → label-BLqV33b1.js} +2 -2
  116. package/dist/{line-BWRi3U3S.js → line-C5s_12ee.js} +3 -3
  117. package/dist/{linear-DnHwODZa.js → linear-2NnK4cxi.js} +2 -2
  118. package/dist/livescript-BEOngLLc.js +4 -0
  119. package/dist/{loader-BvW0-YWZ.js → loader-Dr8Qem8p.js} +1 -1
  120. package/dist/lua-9-7BhQ4Y.js +4 -0
  121. package/dist/main.js +1175 -1142
  122. package/dist/mathematica-DDa0Pfxm.js +4 -0
  123. package/dist/mbox-iO03mmoE.js +4 -0
  124. package/dist/{mermaid-4DMBBIKO-CG1ECj5W.js → mermaid-4DMBBIKO-B7VQMwJx.js} +1 -1
  125. package/dist/{mermaid-CEbzCxCc.js → mermaid-DO-Daq7u.js} +46 -46
  126. package/dist/{mermaid-parser.core-CleJseNW.js → mermaid-parser.core-DreccfmS.js} +7 -7
  127. package/dist/{mhchem-BwoRNwg_.js → mhchem-yiCCuiEF.js} +1 -1
  128. package/dist/{mindmap-definition-VGOIOE7T-CcSYqYP9.js → mindmap-definition-VGOIOE7T-CC1_Vl0f.js} +13 -13
  129. package/dist/mirc-C9z5LT4X.js +4 -0
  130. package/dist/mllike-jGJbdm_C.js +6 -0
  131. package/dist/modelica-DzF7oIEL.js +4 -0
  132. package/dist/mscgen-DB-u125o.js +6 -0
  133. package/dist/mumps-CRTFHhzh.js +4 -0
  134. package/dist/nsis-C4NPTuox.js +4 -0
  135. package/dist/{nsis-B5K1qoyo.js → nsis-ClF3r5Tr.js} +1 -1
  136. package/dist/ntriples-BCOoGph1.js +4 -0
  137. package/dist/{number-overlay-editor-_GnlYFHC.js → number-overlay-editor-CpKi64Fy.js} +1 -1
  138. package/dist/octave-DTwNlazz.js +4 -0
  139. package/dist/{ordinal-2jIulmcR.js → ordinal-B43ZeR68.js} +1 -1
  140. package/dist/oz-DD38AzSz.js +4 -0
  141. package/dist/{packet-DYOGHKS2-CBxXGWNr.js → packet-DYOGHKS2-CmWtF3uO.js} +1 -1
  142. package/dist/pascal-BohSp9jV.js +4 -0
  143. package/dist/perl-f5OutoPM.js +4 -0
  144. package/dist/{pie-VRWISCQL-Bmdnqjip.js → pie-VRWISCQL-B6u8vus8.js} +1 -1
  145. package/dist/{pieDiagram-ADFJNKIX-DNyLF5H2.js → pieDiagram-ADFJNKIX-Di34MOFQ.js} +19 -19
  146. package/dist/pig-Dv7wSmHb.js +4 -0
  147. package/dist/powershell-rYgjKB39.js +4 -0
  148. package/dist/{process-output-Bza_GK7Q.js → process-output-DBYxXdrN.js} +30 -25
  149. package/dist/properties-BFUNLRDN.js +4 -0
  150. package/dist/protobuf-B9QJQPPv.js +4 -0
  151. package/dist/{pug-tjbzJCFk.js → pug-B_rby2yb.js} +1 -1
  152. package/dist/pug-DzvWpaMC.js +4 -0
  153. package/dist/puppet-B_K-n_xK.js +4 -0
  154. package/dist/python-CAiFcaA2.js +4 -0
  155. package/dist/q-cLeFIBLK.js +4 -0
  156. package/dist/{quadrantDiagram-AYHSOK5B-rXwjifrj.js → quadrantDiagram-AYHSOK5B-B9kVk1ny.js} +3 -3
  157. package/dist/r-04Y-Wco3.js +4 -0
  158. package/dist/{radar-ZZBFDIW7-BmCWDffL.js → radar-ZZBFDIW7-XAmXSa8s.js} +1 -1
  159. package/dist/{react-vega-B-rkEqtS.js → react-vega-Cavbrg4l.js} +1 -1
  160. package/dist/{react-vega-k9ODWPlI.js → react-vega-Dh6-UKKe.js} +13 -13
  161. package/dist/{requirementDiagram-UZGBJVZJ-DBdrMVbs.js → requirementDiagram-UZGBJVZJ-BxGfGYEx.js} +13 -13
  162. package/dist/{reveal-component-h0_DDpxE.js → reveal-component-BpKkH57S.js} +10 -10
  163. package/dist/rpm-FUdrIia9.js +5 -0
  164. package/dist/ruby-DMjFXuEW.js +4 -0
  165. package/dist/{sankeyDiagram-TZEHDZUN-CxmzalGv.js → sankeyDiagram-TZEHDZUN-D09PBJ-n.js} +4 -4
  166. package/dist/sas-DzHZxjXK.js +4 -0
  167. package/dist/scheme-DxHd_Rb9.js +4 -0
  168. package/dist/semaphore-CNDGTzkX.js +46 -0
  169. package/dist/{sequenceDiagram-WL72ISMW-CVCDsJ9h.js → sequenceDiagram-WL72ISMW-t_Dpemj0.js} +7 -7
  170. package/dist/shell-C8Kwypgf.js +4 -0
  171. package/dist/sieve-DdyqOKXZ.js +4 -0
  172. package/dist/smalltalk-pB7X1D9y.js +4 -0
  173. package/dist/sparql-NhBO6oOa.js +4 -0
  174. package/dist/{spec-DSIuqd3f.js → spec-hVaaZsY5.js} +4 -4
  175. package/dist/{src-BY0BGg6V.js → src-Bf2iLOlr.js} +1 -1
  176. package/dist/{stateDiagram-FKZM4ZOC-D_2djEhW.js → stateDiagram-FKZM4ZOC-B18gTP_j.js} +16 -16
  177. package/dist/stateDiagram-v2-4FDKWEC3-B6e_t14A.js +29 -0
  178. package/dist/{step-DGAGWg3y.js → step-CWipAYTY.js} +1 -1
  179. package/dist/{strings-B_FOH6eV.js → strings-BiIhGaI8.js} +4 -4
  180. package/dist/style.css +1 -1
  181. package/dist/stylus-SfWSnzPv.js +4 -0
  182. package/dist/swift-jRPdq2zR.js +4 -0
  183. package/dist/{swiper-component-KkEVUDd3.js → swiper-component-DlD2GU2g.js} +2 -2
  184. package/dist/tcl-_hpTHGX3.js +4 -0
  185. package/dist/textile-C9h8slqH.js +4 -0
  186. package/dist/{time-CMdrp3hw.js → time-C1SGcFMH.js} +2 -2
  187. package/dist/{timeline-definition-IT6M3QCI-E4NzxCs3.js → timeline-definition-IT6M3QCI-DJnh1ks5.js} +3 -3
  188. package/dist/{toDate-CHtl9vts.js → toDate-CJWlVNGD.js} +3 -3
  189. package/dist/toml-DWvtinD4.js +4 -0
  190. package/dist/{tooltip-B0mtKTXm.js → tooltip-DRaMBu06.js} +3 -3
  191. package/dist/{treemap-GDKQZRPO-CoKHPxa7.js → treemap-GDKQZRPO-Du95DV6u.js} +1 -1
  192. package/dist/troff-Dwo_A0y7.js +4 -0
  193. package/dist/ttcn-V--CPFKq.js +4 -0
  194. package/dist/ttcn-cfg-CPSMchTG.js +4 -0
  195. package/dist/turtle-B4rPGBWu.js +4 -0
  196. package/dist/{types-DBtDeUKD.js → types-Dzuoc3LN.js} +1 -1
  197. package/dist/{useAsyncData-B6hCGywC.js → useAsyncData-C56Khv_R.js} +1 -1
  198. package/dist/{useDateFormatter-B3mCQMP3.js → useDateFormatter-B_9k85Ex.js} +2 -2
  199. package/dist/{useDeepCompareMemoize-CmwDuYUH.js → useDeepCompareMemoize-Dt98v2ua.js} +1 -1
  200. package/dist/{useIframeCapabilities-DbdLoEDm.js → useIframeCapabilities-BkYHTrss.js} +1 -1
  201. package/dist/{useLifecycle-CjMjllqy.js → useLifecycle-BF6-z62y.js} +3 -3
  202. package/dist/{useTheme-CByZUW0p.js → useTheme-DykuNHR2.js} +2 -2
  203. package/dist/vb-DaMBBd4j.js +4 -0
  204. package/dist/vbscript-BMJQqcE2.js +4 -0
  205. package/dist/{vega-component-CC8TqWWV.js → vega-component-BtvQ-Kc4.js} +26 -24
  206. package/dist/velocity-CGq2QRq2.js +4 -0
  207. package/dist/verilog-CUNo8F5u.js +4 -0
  208. package/dist/vhdl-CCzA0msW.js +4 -0
  209. package/dist/webidl-CqIMDIBL.js +4 -0
  210. package/dist/xquery-XC5Kbr-1.js +4 -0
  211. package/dist/{xychartDiagram-PRI3JC2R-CuxTvjw5.js → xychartDiagram-PRI3JC2R-Dk2d_bX0.js} +10 -10
  212. package/dist/yacas-CGOv7Dzy.js +4 -0
  213. package/dist/z80-CXhVmi-f.js +4 -0
  214. package/dist/{zod-BxdsqRPd.js → zod-BWkcDORu.js} +1 -1
  215. package/package.json +3 -3
  216. package/src/components/chat/chat-components.tsx +47 -0
  217. package/src/components/chat/chat-display.tsx +41 -7
  218. package/src/components/chat/chat-panel.tsx +37 -10
  219. package/src/components/chat/chat-utils.ts +42 -20
  220. package/src/components/chat/reasoning-accordion.tsx +14 -3
  221. package/src/components/chat/tool-call/shared.ts +13 -0
  222. package/src/components/chat/tool-call/tool-approval-card.tsx +62 -0
  223. package/src/components/chat/tool-call/tool-args.tsx +26 -0
  224. package/src/components/chat/tool-call/tool-call-view.tsx +99 -0
  225. package/src/components/chat/tool-call/tool-error-card.tsx +81 -0
  226. package/src/components/chat/tool-call/tool-history-row.tsx +153 -0
  227. package/src/components/chat/tool-call/tool-result.tsx +101 -0
  228. package/src/components/data-table/TableTopBar.tsx +5 -1
  229. package/src/components/data-table/data-table.tsx +5 -0
  230. package/src/components/data-table/download-policy/atoms.ts +10 -0
  231. package/src/components/data-table/export-actions.tsx +31 -4
  232. package/src/components/editor/actions/useNotebookActions.tsx +3 -1
  233. package/src/components/editor/app-container.tsx +7 -1
  234. package/src/components/editor/chrome/panels/context-aware-panel/context-aware-panel.tsx +10 -2
  235. package/src/components/editor/chrome/wrapper/app-chrome.tsx +1 -0
  236. package/src/components/editor/chrome/wrapper/footer.tsx +4 -1
  237. package/src/components/editor/chrome/wrapper/panels.tsx +4 -1
  238. package/src/components/editor/chrome/wrapper/sidebar.tsx +4 -1
  239. package/src/components/editor/controls/Controls.tsx +11 -3
  240. package/src/components/editor/file-tree/requesting-tree.tsx +27 -25
  241. package/src/components/editor/file-tree/upload.tsx +23 -24
  242. package/src/components/editor/header/__tests__/status.test.tsx +108 -0
  243. package/src/components/editor/header/status.tsx +44 -10
  244. package/src/components/editor/navigation/__tests__/clipboard.test.ts +106 -0
  245. package/src/components/editor/navigation/__tests__/navigation.test.ts +70 -0
  246. package/src/components/editor/navigation/clipboard.ts +99 -25
  247. package/src/components/editor/navigation/navigation.ts +15 -1
  248. package/src/components/editor/notebook-cell.tsx +3 -0
  249. package/src/components/home/components.tsx +6 -0
  250. package/src/components/pages/run-page.tsx +4 -1
  251. package/src/core/ai/tools/__tests__/registry.test.ts +10 -12
  252. package/src/core/ai/tools/registry.ts +9 -5
  253. package/src/core/cells/__tests__/cells.test.ts +187 -0
  254. package/src/core/cells/__tests__/pending-cut-service.test.tsx +123 -0
  255. package/src/core/cells/cells.ts +102 -17
  256. package/src/core/cells/document-changes.ts +6 -1
  257. package/src/core/cells/pending-cut-service.ts +55 -0
  258. package/src/core/cells/utils.ts +11 -0
  259. package/src/core/codemirror/cells/extensions.ts +10 -0
  260. package/src/core/codemirror/markdown/__tests__/commands.test.ts +3 -3
  261. package/src/core/codemirror/markdown/commands.ts +1 -2
  262. package/src/core/edit-app.tsx +2 -1
  263. package/src/core/hotkeys/hotkeys.ts +5 -0
  264. package/src/core/islands/worker/worker.tsx +3 -2
  265. package/src/core/network/requests-network.ts +21 -3
  266. package/src/core/network/types.ts +12 -1
  267. package/src/core/run-app.tsx +2 -1
  268. package/src/core/wasm/__tests__/utils.test.ts +34 -0
  269. package/src/core/wasm/bridge.ts +14 -1
  270. package/src/core/wasm/utils.ts +14 -0
  271. package/src/core/wasm/worker/bootstrap.ts +3 -2
  272. package/src/core/wasm/worker/worker.ts +3 -2
  273. package/src/core/websocket/__tests__/useMarimoKernelConnection.hook.test.tsx +155 -0
  274. package/src/core/websocket/__tests__/useMarimoKernelConnection.test.ts +137 -0
  275. package/src/core/websocket/transports/basic.ts +2 -0
  276. package/src/core/websocket/transports/transport.ts +1 -0
  277. package/src/core/websocket/useMarimoKernelConnection.tsx +130 -55
  278. package/src/core/websocket/useWebSocket.tsx +5 -2
  279. package/src/css/app/Cell.css +10 -0
  280. package/src/plugins/impl/DataTablePlugin.tsx +12 -0
  281. package/src/plugins/impl/TabsPlugin.tsx +35 -7
  282. package/src/plugins/impl/__tests__/TabsPlugin.test.tsx +154 -0
  283. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +6 -0
  284. package/src/plugins/impl/vega/resolve-data.ts +8 -1
  285. package/src/utils/__tests__/id-tree.test.ts +71 -0
  286. package/src/utils/__tests__/semaphore.test.ts +218 -0
  287. package/src/utils/fileToBase64.ts +8 -7
  288. package/src/utils/id-tree.tsx +89 -0
  289. package/src/utils/semaphore.ts +88 -0
  290. package/dist/apl-BKoVld9y.js +0 -4
  291. package/dist/asciiarmor-DQrKIjoo.js +0 -4
  292. package/dist/asn1-BZvnj0dq.js +0 -4
  293. package/dist/assets/__vite-browser-external-rrUYDKRl.js +0 -1
  294. package/dist/assets/worker-Bfy15ViQ.js +0 -73
  295. package/dist/brainfuck-D558nlUv.js +0 -4
  296. package/dist/classDiagram-2ON5EDUG-CBHMR6ZU.js +0 -30
  297. package/dist/classDiagram-v2-WZHVMYZB-BsUtUGM_.js +0 -30
  298. package/dist/clojure-Cq8mTSrE.js +0 -4
  299. package/dist/cmake-D8HCovWK.js +0 -4
  300. package/dist/cobol-UolN-9iU.js +0 -4
  301. package/dist/coffeescript-VdNuWrt5.js +0 -4
  302. package/dist/commonlisp-ALX7fpDc.js +0 -4
  303. package/dist/crystal-PbyO9Q_s.js +0 -4
  304. package/dist/css-DFklJkr_.js +0 -4
  305. package/dist/cypher-BifNeYlv.js +0 -4
  306. package/dist/d-BA-JP4PJ.js +0 -4
  307. package/dist/diff-CtkDpav4.js +0 -4
  308. package/dist/dist-BuBwsFva.js +0 -5
  309. package/dist/dist-BzmEQ9u7.js +0 -5
  310. package/dist/dist-Cih01ssx.js +0 -5
  311. package/dist/dist-CqfONiY9.js +0 -5
  312. package/dist/dist-D0iD0Fi9.js +0 -5
  313. package/dist/dist-DtNLXm8d.js +0 -5
  314. package/dist/dtd-DW3_UFEG.js +0 -4
  315. package/dist/dylan-pDhodO2N.js +0 -4
  316. package/dist/ecl-BJT8-YD7.js +0 -4
  317. package/dist/eiffel-Dmns-9vS.js +0 -4
  318. package/dist/elm-Da4sO4Bz.js +0 -4
  319. package/dist/erlang-C-zBsDi7.js +0 -4
  320. package/dist/factor-4xPWlWB5.js +0 -4
  321. package/dist/forth-l-c75zSd.js +0 -4
  322. package/dist/fortran-DIujSODW.js +0 -4
  323. package/dist/gas-CXnG5g_b.js +0 -4
  324. package/dist/gherkin-VPeqd4-X.js +0 -4
  325. package/dist/groovy-CphhZQgg.js +0 -4
  326. package/dist/haskell-CCvlS5Iq.js +0 -4
  327. package/dist/haxe-C_bi66fP.js +0 -5
  328. package/dist/idl-1DcP4Dm8.js +0 -4
  329. package/dist/javascript-DUIGhBvO.js +0 -4
  330. package/dist/julia-Cs2G4PQi.js +0 -4
  331. package/dist/livescript-DMtVFaAN.js +0 -4
  332. package/dist/lua-BAoLtdJg.js +0 -4
  333. package/dist/mathematica-C_NoFtbo.js +0 -4
  334. package/dist/mbox-DcFJFYrH.js +0 -4
  335. package/dist/mirc-71dccf_u.js +0 -4
  336. package/dist/mllike-CWcOFVDq.js +0 -6
  337. package/dist/modelica-Ape2VXxx.js +0 -4
  338. package/dist/mscgen-Cc6TwbSN.js +0 -6
  339. package/dist/mumps-h-ZbdkJ9.js +0 -4
  340. package/dist/nsis-C0p3m7JW.js +0 -4
  341. package/dist/ntriples-c9lEeT5w.js +0 -4
  342. package/dist/octave-DzEgB_74.js +0 -4
  343. package/dist/oz-CAxvHkyQ.js +0 -4
  344. package/dist/pascal-BJzu1sgP.js +0 -4
  345. package/dist/perl--IrOzZ2Z.js +0 -4
  346. package/dist/pig-CiBKKNhC.js +0 -4
  347. package/dist/powershell-KY0j6Qop.js +0 -4
  348. package/dist/properties-BW8q3ziV.js +0 -4
  349. package/dist/protobuf-BGaeuTGV.js +0 -4
  350. package/dist/pug-DjOKK-4J.js +0 -4
  351. package/dist/puppet-DWm2o6zX.js +0 -4
  352. package/dist/python-Bp2gezZy.js +0 -4
  353. package/dist/q-DljPshos.js +0 -4
  354. package/dist/r-BajPMnEu.js +0 -4
  355. package/dist/rpm-BKx-ZZ62.js +0 -5
  356. package/dist/ruby-DJq_HNKc.js +0 -4
  357. package/dist/sas-WANvpcOU.js +0 -4
  358. package/dist/scheme-CliBbhGF.js +0 -4
  359. package/dist/shell-BwhrNUvM.js +0 -4
  360. package/dist/sieve-BIVePvMp.js +0 -4
  361. package/dist/smalltalk-D6G48JmY.js +0 -4
  362. package/dist/sparql-jjc3BmEP.js +0 -4
  363. package/dist/stateDiagram-v2-4FDKWEC3-Cv9Av10H.js +0 -29
  364. package/dist/stylus-WPBPQ4PE.js +0 -4
  365. package/dist/swift-O1Qy6iCm.js +0 -4
  366. package/dist/tcl-BAFdhvsi.js +0 -4
  367. package/dist/textile-DFuzhNLG.js +0 -4
  368. package/dist/toml-DRSTeely.js +0 -4
  369. package/dist/troff-B_ZjwBW0.js +0 -4
  370. package/dist/ttcn-CAyiB3ic.js +0 -4
  371. package/dist/ttcn-cfg-BS5_BGBJ.js +0 -4
  372. package/dist/turtle-CUBEDy3E.js +0 -4
  373. package/dist/vb-DY9S6-U2.js +0 -4
  374. package/dist/vbscript-gaHC39Jq.js +0 -4
  375. package/dist/velocity-TfCOtJZ_.js +0 -4
  376. package/dist/verilog-c2JOX8mv.js +0 -4
  377. package/dist/vhdl-dHBirRiO.js +0 -4
  378. package/dist/webidl-Bauj-i07.js +0 -4
  379. package/dist/xquery-CtaEAOt8.js +0 -4
  380. package/dist/yacas-BZ85agQP.js +0 -4
  381. package/dist/z80-hCgR-L4U.js +0 -4
  382. package/src/components/chat/tool-call-accordion.tsx +0 -247
  383. /package/dist/{ImageComparisonComponent-DaocPIse.js → ImageComparisonComponent-CNHIsPDj.js} +0 -0
  384. /package/dist/{Plot-PIeIvFnD.js → Plot-4wn-lMVn.js} +0 -0
  385. /package/dist/{apl-Dt8GMXYg.js → apl-BCgCq9iM.js} +0 -0
  386. /package/dist/{array-B-MVxRIF.js → array-tvvEqPy7.js} +0 -0
  387. /package/dist/{asciiarmor-CitDQ85h.js → asciiarmor-BtqU-KJQ.js} +0 -0
  388. /package/dist/{asn1-abrf9SMK.js → asn1-Dmb-dTMx.js} +0 -0
  389. /package/dist/{asterisk-BUZwqih-.js → asterisk-DaVJJDnV.js} +0 -0
  390. /package/dist/{brainfuck-BL-Boof0.js → brainfuck-C1HoZKlE.js} +0 -0
  391. /package/dist/{chunk-4F5CHEZ2-C6tO9vjs.js → chunk-4F5CHEZ2-BZq7Kom7.js} +0 -0
  392. /package/dist/{chunk-B2363JML-Ds8wZXyP.js → chunk-B2363JML-D9-XOau1.js} +0 -0
  393. /package/dist/{chunk-DR5Q36YT-CP69aZS_.js → chunk-DR5Q36YT-BflwErH1.js} +0 -0
  394. /package/dist/{chunk-FRFDVMJY-BgQv1HBE.js → chunk-FRFDVMJY-BSBUAX7r.js} +0 -0
  395. /package/dist/{chunk-PL6DKKU2-DHfTUHy8.js → chunk-PL6DKKU2-B0MTXvyc.js} +0 -0
  396. /package/dist/{chunk-SJTYNZTY-Diciw4sx.js → chunk-SJTYNZTY-CEG4F0pB.js} +0 -0
  397. /package/dist/{chunk-TQ3KTPDO-CQfP9npd.js → chunk-TQ3KTPDO-DiCtqVSi.js} +0 -0
  398. /package/dist/{chunk-UMXZTB3W-MSKeGL7W.js → chunk-UMXZTB3W-97iS1iEl.js} +0 -0
  399. /package/dist/{click-outside-container-BZgN7xS_.js → click-outside-container-BDd67_1U.js} +0 -0
  400. /package/dist/{clike-RWg7anhx.js → clike-CdT0yHjt.js} +0 -0
  401. /package/dist/{clojure-DaojKHow.js → clojure-CdyrCpUv.js} +0 -0
  402. /package/dist/{cmake-DN-_v0XE.js → cmake-BFlPxym7.js} +0 -0
  403. /package/dist/{cobol-C3VpMyux.js → cobol-CcJXewp8.js} +0 -0
  404. /package/dist/{coffeescript-DIkz3Tbt.js → coffeescript-DnKuIKRo.js} +0 -0
  405. /package/dist/{colors-Cn2p_FA3.js → colors-CQAOa8cK.js} +0 -0
  406. /package/dist/{common-keywords-hbLeU7VU.js → common-keywords-FBrXPTcz.js} +0 -0
  407. /package/dist/{commonlisp-CB1boOiP.js → commonlisp-B-kok83Z.js} +0 -0
  408. /package/dist/{crystal-DI2oCml6.js → crystal-FYRYjI1I.js} +0 -0
  409. /package/dist/{css-BdEVwQDV.js → css-B45lc2V3.js} +0 -0
  410. /package/dist/{cypher-BNHToqxU.js → cypher-DZMLyVY_.js} +0 -0
  411. /package/dist/{cytoscape.esm-WbbDoCfu.js → cytoscape.esm-ayF70frT.js} +0 -0
  412. /package/dist/{d-D7we7I1b.js → d-x-VVT4o9.js} +0 -0
  413. /package/dist/{diff-Cia6fzjN.js → diff-Dxe2mpXk.js} +0 -0
  414. /package/dist/{dist-BK-3fF4P.js → dist-B4LJpMEg.js} +0 -0
  415. /package/dist/{dist-CxdUraQr.js → dist-B507mf_I.js} +0 -0
  416. /package/dist/{dist-C89sHDXk.js → dist-BGdYVvOu.js} +0 -0
  417. /package/dist/{dist-DquyVv5H.js → dist-BNyrZfqT.js} +0 -0
  418. /package/dist/{dist-Zn0KNbo9.js → dist-Bc5pmZIw.js} +0 -0
  419. /package/dist/{dist-C-J0pt5p.js → dist-BvCfQQQE.js} +0 -0
  420. /package/dist/{dist-D9r7Cmw7.js → dist-C2ej4eOH.js} +0 -0
  421. /package/dist/{dist-HVuryI1a.js → dist-C34oIrQ9.js} +0 -0
  422. /package/dist/{dist-CGLzXdrt.js → dist-CDFZi-QD.js} +0 -0
  423. /package/dist/{dist-C9fmTOin.js → dist-CYEylvZA.js} +0 -0
  424. /package/dist/{dist-DadjmS-4.js → dist-DJ6zJQZ4.js} +0 -0
  425. /package/dist/{dist-CtCY55Jf.js → dist-Dh3wkoyH.js} +0 -0
  426. /package/dist/{dist-C474qFoq.js → dist-Dhk6FMb0.js} +0 -0
  427. /package/dist/{dist-DZjQ_MBo.js → dist-KnujRhFL.js} +0 -0
  428. /package/dist/{dist-CinA9Enb.js → dist-WdPUFc56.js} +0 -0
  429. /package/dist/{dist-DBLeRrPp.js → dist-t_qL7eB8.js} +0 -0
  430. /package/dist/{dist-CyFFzJTb.js → dist-usPCDYx8.js} +0 -0
  431. /package/dist/{dtd-H4Hubdwp.js → dtd-C9VM_Wfu.js} +0 -0
  432. /package/dist/{duckdb-keywords-CZ_ZTscu.js → duckdb-keywords-CvJhR_Yd.js} +0 -0
  433. /package/dist/{dylan-fVO6rnq3.js → dylan-DTSnEIFO.js} +0 -0
  434. /package/dist/{ebnf-WEXPLEWb.js → ebnf-2D4Ctp3y.js} +0 -0
  435. /package/dist/{ecl-B94VPjNR.js → ecl-N04ptnRK.js} +0 -0
  436. /package/dist/{eiffel-C_R6TusS.js → eiffel-Dd8rpqr_.js} +0 -0
  437. /package/dist/{elm-DzCHbO2g.js → elm-GT2E866W.js} +0 -0
  438. /package/dist/{erlang-BGNkx6JU.js → erlang-Cf0Bp5pY.js} +0 -0
  439. /package/dist/{esm-Bb_hbWan.js → esm-BaaaPNGl.js} +0 -0
  440. /package/dist/{fcl-B_Gv5Jfx.js → fcl-Ccj8Z5Xd.js} +0 -0
  441. /package/dist/{forth-Bybw0cJ7.js → forth-wd_XzGTg.js} +0 -0
  442. /package/dist/{fortran-C6PoCLkI.js → fortran-DcwUTZFe.js} +0 -0
  443. /package/dist/{gas-BBlhenj4.js → gas-DeALIER3.js} +0 -0
  444. /package/dist/{gherkin-NXtNG85X.js → gherkin-CKTqaJNX.js} +0 -0
  445. /package/dist/{groovy-BoFYK9xM.js → groovy-Bwdp_d8D.js} +0 -0
  446. /package/dist/{haskell-BtBdvQ1n.js → haskell-DCdCcPLK.js} +0 -0
  447. /package/dist/{haxe-D--o6dr0.js → haxe-DAyktQWJ.js} +0 -0
  448. /package/dist/{http-Dc2fv19V.js → http-_DVAYWoR.js} +0 -0
  449. /package/dist/{idl-AqTq5l7e.js → idl-CBuZiRYu.js} +0 -0
  450. /package/dist/{init-D-g0ONX1.js → init-uv0kkh4g.js} +0 -0
  451. /package/dist/{javascript-DvwNVye9.js → javascript-DzigE11c.js} +0 -0
  452. /package/dist/{julia-DoKiagZC.js → julia-PwfB-0Cm.js} +0 -0
  453. /package/dist/{katex-B7pMJpE0.js → katex-C_XRmjAP.js} +0 -0
  454. /package/dist/{livescript-DxBZMiWB.js → livescript-BJLz1EbT.js} +0 -0
  455. /package/dist/{lua-DmS_0NTu.js → lua-ZC-XC2jf.js} +0 -0
  456. /package/dist/{math-BYK36kWZ.js → math-DFcdCCU8.js} +0 -0
  457. /package/dist/{mathematica-ChlDFeIC.js → mathematica-DCYMx6qB.js} +0 -0
  458. /package/dist/{mbox-CguZuODr.js → mbox-OxMK_9XI.js} +0 -0
  459. /package/dist/{mirc-CFtY8dqz.js → mirc-nJVyhA0H.js} +0 -0
  460. /package/dist/{mllike-C0EJrEOk.js → mllike-DRO89bsU.js} +0 -0
  461. /package/dist/{modelica-C1kO1nfS.js → modelica-Don3E6ZD.js} +0 -0
  462. /package/dist/{mscgen-DEYdr7AY.js → mscgen-DJfqD3bN.js} +0 -0
  463. /package/dist/{mumps-B3NVJs2V.js → mumps-SjGTvDYL.js} +0 -0
  464. /package/dist/{nginx-ComVAAGN.js → nginx-DasThI7R.js} +0 -0
  465. /package/dist/{node-sql-parser-DNGGJ-Rw.js → node-sql-parser-B8nBD36q.js} +0 -0
  466. /package/dist/{ntriples-DHol9X9H.js → ntriples-CNBKRl3I.js} +0 -0
  467. /package/dist/{octave-CYGz0bfo.js → octave-DdeVHNlx.js} +0 -0
  468. /package/dist/{oz-kPxb2ni5.js → oz-CcKSoNvN.js} +0 -0
  469. /package/dist/{pascal-bZ0yrJKy.js → pascal-6leftwNj.js} +0 -0
  470. /package/dist/{path-Du6n3sOU.js → path-BGaWgPKg.js} +0 -0
  471. /package/dist/{perl-z4hvqyqz.js → perl-BhJIwWzN.js} +0 -0
  472. /package/dist/{pig-DZO8QDF9.js → pig-r-xDHqRf.js} +0 -0
  473. /package/dist/{powershell-BSuaDQEC.js → powershell-D-BELeNi.js} +0 -0
  474. /package/dist/{properties-BXhGLlIx.js → properties-CnuDhbll.js} +0 -0
  475. /package/dist/{protobuf-DM6iybWV.js → protobuf-CO8RBhvX.js} +0 -0
  476. /package/dist/{puppet-Bn05sQT8.js → puppet-NmXHjLy8.js} +0 -0
  477. /package/dist/{python-Cvnhm0g7.js → python-DAQXi720.js} +0 -0
  478. /package/dist/{q-B9V8hzex.js → q-DlikXfV0.js} +0 -0
  479. /package/dist/{r-Cf0gFqmq.js → r-CuohilwT.js} +0 -0
  480. /package/dist/{rpm-D-LMkTV1.js → rpm-0Pjwp0Pb.js} +0 -0
  481. /package/dist/{ruby-DeuPikpK.js → ruby-Dq8NJTDG.js} +0 -0
  482. /package/dist/{sas-C9tjgAo9.js → sas-CuwonyVP.js} +0 -0
  483. /package/dist/{scheme-D1_bUF0G.js → scheme-CYU-RRIf.js} +0 -0
  484. /package/dist/{shell-CJBmnks3.js → shell-COPmX2qE.js} +0 -0
  485. /package/dist/{sieve-1fSV75CF.js → sieve-B_3zyLne.js} +0 -0
  486. /package/dist/{simple-mode-B90Wdavj.js → simple-mode-DSBniks8.js} +0 -0
  487. /package/dist/{smalltalk-sZNPD0HO.js → smalltalk-DRft7iPv.js} +0 -0
  488. /package/dist/{solr-DTkyqJ-Z.js → solr-RZ9uTl59.js} +0 -0
  489. /package/dist/{sparql-oHc1nm77.js → sparql-CN6qj55H.js} +0 -0
  490. /package/dist/{spreadsheet-CER0raqY.js → spreadsheet-BNNUNXA2.js} +0 -0
  491. /package/dist/{sql-ByOoEONQ.js → sql-B4x8IkwU.js} +0 -0
  492. /package/dist/{stylus-KzkX6zRB.js → stylus-Bn_ZjOQ3.js} +0 -0
  493. /package/dist/{swift-DqVxZvKo.js → swift-BLUJhMbz.js} +0 -0
  494. /package/dist/{tcl-BtWSwXfA.js → tcl-C86fxecl.js} +0 -0
  495. /package/dist/{textile-CWDbn9Ql.js → textile-DmHh2rsK.js} +0 -0
  496. /package/dist/{tiddlywiki-Cr9xyOY1.js → tiddlywiki-DI0mF2WJ.js} +0 -0
  497. /package/dist/{tiki-D5JONyfZ.js → tiki-2HU6XLLn.js} +0 -0
  498. /package/dist/{timer-D7JVdX9U.js → timer-YZl28NYN.js} +0 -0
  499. /package/dist/{toml-BfehlgmL.js → toml-GWANRNAD.js} +0 -0
  500. /package/dist/{treemap-qFGzn7xk.js → treemap-D-ka1hvx.js} +0 -0
  501. /package/dist/{troff-BZBk6AAu.js → troff-BHTsomIy.js} +0 -0
  502. /package/dist/{ttcn-DVwvXg0_.js → ttcn-DQuhn5Mn.js} +0 -0
  503. /package/dist/{ttcn-cfg-gjbVLf1L.js → ttcn-cfg-HjFYtdB-.js} +0 -0
  504. /package/dist/{turtle-CgxKXorV.js → turtle-nCay33Nv.js} +0 -0
  505. /package/dist/{vb-B9kSwTdM.js → vb-BG-XlqqJ.js} +0 -0
  506. /package/dist/{vbscript-DrUKSCdb.js → vbscript-B6vyW0-D.js} +0 -0
  507. /package/dist/{velocity-AlMYTnMy.js → velocity-CWegueqO.js} +0 -0
  508. /package/dist/{verilog-DLUaM05j.js → verilog-CzSQm4cG.js} +0 -0
  509. /package/dist/{vhdl-DUJOtSmO.js → vhdl-DqnNVL7r.js} +0 -0
  510. /package/dist/{webidl-CQp4aHk_.js → webidl-DXEUpDWH.js} +0 -0
  511. /package/dist/{xquery-IxkjlwOD.js → xquery-Ba_NB5bD.js} +0 -0
  512. /package/dist/{yacas-Bnctn5w8.js → yacas-HKQU6hyk.js} +0 -0
  513. /package/dist/{z80-DrFwhx53.js → z80-CXkHXLdj.js} +0 -0
@@ -65,6 +65,17 @@ export function canUndoDeletes(state: NotebookState) {
65
65
  return state.history.length > 0;
66
66
  }
67
67
 
68
+ /**
69
+ * Label for the undo action based on the last history entry type.
70
+ */
71
+ export function getUndoLabel(state: NotebookState): string {
72
+ const last = state.history[state.history.length - 1];
73
+ if (!last) {
74
+ return "Undo cell deletion";
75
+ }
76
+ return last.type === "move" ? "Undo move" : "Undo cell deletion";
77
+ }
78
+
68
79
  /**
69
80
  * Get the status of the descendants of the given cell.
70
81
  */
@@ -10,6 +10,10 @@ import {
10
10
  } from "@codemirror/view";
11
11
  import { createTracebackInfoAtom } from "@/core/cells/cells";
12
12
  import { type CellId, HTMLCellId, SCRATCH_CELL_ID } from "@/core/cells/ids";
13
+ import {
14
+ clearPendingCutAtom,
15
+ pendingCutCellIdsAtom,
16
+ } from "@/core/cells/pending-cut-service";
13
17
  import { loroSyncAnnotation } from "@/core/codemirror/rtc/loro/sync";
14
18
  import type { KeymapConfig } from "@/core/config/config-schema";
15
19
  import type { HotkeyProvider } from "@/core/hotkeys/hotkeys";
@@ -326,6 +330,12 @@ function cellCodeEditing(hotkeys: HotkeyProvider): Extension[] {
326
330
  code: nextCode,
327
331
  formattingChange: isFormattingChange,
328
332
  });
333
+
334
+ // Clear pending cut state if this cell was marked for cut
335
+ const pendingCutCellIds = store.get(pendingCutCellIdsAtom);
336
+ if (pendingCutCellIds.has(cellId)) {
337
+ store.set(clearPendingCutAtom);
338
+ }
329
339
  }
330
340
  });
331
341
 
@@ -246,7 +246,7 @@ describe("insertImage", () => {
246
246
  path: "public",
247
247
  type: "file",
248
248
  name: "hello.png",
249
- contents: "AQID",
249
+ file: expect.any(File),
250
250
  });
251
251
 
252
252
  expect(view.state.doc.toString()).toMatchInlineSnapshot(
@@ -291,7 +291,7 @@ describe("insertImage", () => {
291
291
  path: "nested/public", // store in public folder of notebook directory
292
292
  type: "file",
293
293
  name: "hello.png",
294
- contents: "AQID",
294
+ file: expect.any(File),
295
295
  });
296
296
 
297
297
  expect(view.state.doc.toString()).toMatchInlineSnapshot(
@@ -337,7 +337,7 @@ describe("insertImage", () => {
337
337
  path: "/Users/user/Development/project/public",
338
338
  type: "file",
339
339
  name: "hello.png",
340
- contents: "AQID",
340
+ file: expect.any(File),
341
341
  });
342
342
 
343
343
  // Should convert absolute path to relative path
@@ -313,7 +313,6 @@ export async function insertImage(view: EditorView, file: File) {
313
313
  // If the file is base64 encoded, we can save it locally to prevent large file strings
314
314
  try {
315
315
  if (dataUrl.startsWith("data:")) {
316
- const base64 = dataUrl.split(",")[1];
317
316
  let inputFilename = prompt(
318
317
  "We can save your image as a file. Enter a filename.",
319
318
  file.name,
@@ -348,7 +347,7 @@ export async function insertImage(view: EditorView, file: File) {
348
347
  path: publicFolderPath as FilePath,
349
348
  type: "file",
350
349
  name: inputFilename,
351
- contents: base64,
350
+ file,
352
351
  });
353
352
 
354
353
  if (createFileRes.success) {
@@ -79,7 +79,7 @@ export const EditApp: React.FC<AppProps> = ({
79
79
  };
80
80
  }, []);
81
81
 
82
- const { connection } = useMarimoKernelConnection({
82
+ const { connection, reconnect } = useMarimoKernelConnection({
83
83
  autoInstantiate: userConfig.runtime.auto_instantiate,
84
84
  setCells: (cells, layout) => {
85
85
  setCells(cells);
@@ -147,6 +147,7 @@ export const EditApp: React.FC<AppProps> = ({
147
147
  connection={connection}
148
148
  isRunning={isRunning}
149
149
  width={appConfig.width}
150
+ onReconnect={reconnect}
150
151
  >
151
152
  <AppHeader
152
153
  connection={connection}
@@ -442,6 +442,11 @@ const DEFAULT_HOT_KEY = {
442
442
  group: "Command",
443
443
  key: "c",
444
444
  },
445
+ "command.cutCell": {
446
+ name: "Cut cell",
447
+ group: "Command",
448
+ key: "x",
449
+ },
445
450
  "command.pasteCell": {
446
451
  name: "Paste cell",
447
452
  group: "Command",
@@ -9,6 +9,7 @@ import {
9
9
  } from "rpc-anywhere";
10
10
  import type { NotificationPayload } from "@/core/kernel/messages";
11
11
  import type { ParentSchema } from "@/core/wasm/rpc";
12
+ import { shouldLoadDuckDBPackages } from "@/core/wasm/utils";
12
13
  import { TRANSPORT_ID } from "@/core/wasm/worker/constants";
13
14
  import { getPyodideVersion } from "@/core/wasm/worker/getPyodideVersion";
14
15
  import { MessageBuffer } from "@/core/wasm/worker/message-buffer";
@@ -85,8 +86,8 @@ const requestHandler = createRPCRequestHandler({
85
86
  loadPackages: async (code: string) => {
86
87
  await pyodideReadyPromise; // Make sure loading is done
87
88
 
88
- if (code.includes("mo.sql")) {
89
- // Add pandas and duckdb to the code
89
+ if (shouldLoadDuckDBPackages(code)) {
90
+ // Add pandas and duckdb to the code for mo.sql and for remote duckdb sources
90
91
  code = `import pandas\n${code}`;
91
92
  code = `import duckdb\n${code}`;
92
93
  code = `import sqlglot\n${code}`;
@@ -6,6 +6,19 @@ import { API, createClientWithRuntimeManager } from "./api";
6
6
  import { waitForConnectionOpen } from "./connection";
7
7
  import type { EditRequests, RunRequests } from "./types";
8
8
 
9
+ /**
10
+ * Options for POSTing FormData via openapi-fetch. openapi-fetch types
11
+ * request bodies from the JSON schema, so we bypass the body type and
12
+ * override the serializer to pass the FormData through unchanged; the
13
+ * browser then sets the multipart Content-Type with boundary.
14
+ */
15
+ function multipartInit(formData: FormData) {
16
+ return {
17
+ body: formData as never,
18
+ bodySerializer: (body: unknown) => body as never,
19
+ };
20
+ }
21
+
9
22
  const { handleResponse, handleResponseReturnNull } = API;
10
23
 
11
24
  export function createNetworkRequests(): EditRequests & RunRequests {
@@ -298,10 +311,15 @@ export function createNetworkRequests(): EditRequests & RunRequests {
298
311
  },
299
312
  sendCreateFileOrFolder: async (request) => {
300
313
  await waitForConnectionOpen();
314
+ const formData = new FormData();
315
+ formData.append("path", request.path);
316
+ formData.append("type", request.type);
317
+ formData.append("name", request.name);
318
+ if (request.file) {
319
+ formData.append("file", request.file, request.name);
320
+ }
301
321
  return getClient()
302
- .POST("/api/files/create", {
303
- body: request,
304
- })
322
+ .POST("/api/files/create", multipartInit(formData))
305
323
  .then(handleResponse);
306
324
  },
307
325
  sendDeleteFileOrFolder: async (request) => {
@@ -80,6 +80,17 @@ export type SaveUserConfigurationRequest =
80
80
  export interface SetCellConfigRequest {
81
81
  configs: Record<CellId, Partial<CellConfig>>;
82
82
  }
83
+ /**
84
+ * Client-side shape for creating a file/directory/notebook. The HTTP
85
+ * transport sends this as multipart/form-data; the WASM bridge base64-encodes
86
+ * `file` internally and crosses the JS<->Py boundary as JSON.
87
+ */
88
+ export interface FileCreateInput {
89
+ path: string;
90
+ type: "file" | "directory" | "notebook";
91
+ name: string;
92
+ file?: Blob;
93
+ }
83
94
  export type UpdateUIElementRequest = schemas["UpdateUIElementRequest"];
84
95
  export type ModelRequest = schemas["ModelRequest"];
85
96
  export type NotebookDocumentTransactionRequest =
@@ -165,7 +176,7 @@ export interface EditRequests {
165
176
  sendListFiles: (request: FileListRequest) => Promise<FileListResponse>;
166
177
  sendSearchFiles: (request: FileSearchRequest) => Promise<FileSearchResponse>;
167
178
  sendCreateFileOrFolder: (
168
- request: FileCreateRequest,
179
+ request: FileCreateInput,
169
180
  ) => Promise<FileCreateResponse>;
170
181
  sendDeleteFileOrFolder: (
171
182
  request: FileDeleteRequest,
@@ -38,7 +38,7 @@ export const RunApp: React.FC<AppProps> = ({ appConfig }) => {
38
38
  };
39
39
  }, []);
40
40
 
41
- const { connection } = useMarimoKernelConnection({
41
+ const { connection, reconnect } = useMarimoKernelConnection({
42
42
  autoInstantiate: true,
43
43
  setCells: setCells,
44
44
  sessionId: getSessionId(),
@@ -84,6 +84,7 @@ export const RunApp: React.FC<AppProps> = ({ appConfig }) => {
84
84
  connection={connection}
85
85
  isRunning={isRunning}
86
86
  width={appConfig.width}
87
+ onReconnect={reconnect}
87
88
  >
88
89
  <AppHeader connection={connection} className="sm:pt-8">
89
90
  {galleryHref && (
@@ -0,0 +1,34 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { describe, expect, it } from "vitest";
4
+ import { shouldLoadDuckDBPackages } from "../utils";
5
+
6
+ describe("shouldLoadDuckDBPackages", () => {
7
+ it("loads for mo.sql", () => {
8
+ expect(shouldLoadDuckDBPackages('df = mo.sql("SELECT 1")')).toBe(true);
9
+ });
10
+
11
+ it("loads for duckdb imports and usage", () => {
12
+ expect(shouldLoadDuckDBPackages("import duckdb")).toBe(true);
13
+ expect(shouldLoadDuckDBPackages("from duckdb import sql")).toBe(true);
14
+ expect(shouldLoadDuckDBPackages("import pandas, duckdb")).toBe(true);
15
+ expect(shouldLoadDuckDBPackages("rows = duckdb.sql('SELECT 1')")).toBe(
16
+ true,
17
+ );
18
+ });
19
+
20
+ it("loads when package discovery found duckdb", () => {
21
+ expect(
22
+ shouldLoadDuckDBPackages("print('hello')", new Set(["duckdb"])),
23
+ ).toBe(true);
24
+ });
25
+
26
+ it("does not load for incidental duckdb text", () => {
27
+ expect(shouldLoadDuckDBPackages("name = 'duckdb'")).toBe(false);
28
+ expect(shouldLoadDuckDBPackages("# import duckdb")).toBe(false);
29
+ });
30
+
31
+ it("does not load without mo.sql, duckdb usage, or discovery", () => {
32
+ expect(shouldLoadDuckDBPackages("print('hello')")).toBe(false);
33
+ });
34
+ });
@@ -3,6 +3,7 @@
3
3
 
4
4
  import { toast } from "@/components/ui/use-toast";
5
5
  import { userConfigAtom } from "@/core/config/config";
6
+ import { serializeBlob } from "@/utils/blob";
6
7
  import { Deferred } from "@/utils/Deferred";
7
8
  import { throwNotImplemented } from "@/utils/functions";
8
9
  import { Logger } from "@/utils/Logger";
@@ -431,9 +432,21 @@ export class PyodideBridge implements RunRequests, EditRequests {
431
432
  sendCreateFileOrFolder: EditRequests["sendCreateFileOrFolder"] = async (
432
433
  request,
433
434
  ) => {
435
+ // The WASM RPC boundary can only carry JSON, so we base64-encode the
436
+ // file bytes here. The HTTP transport uses multipart/form-data instead.
437
+ let contents: string | null = null;
438
+ if (request.file) {
439
+ const dataUrl = await serializeBlob(request.file);
440
+ contents = dataUrl.split(",")[1] ?? "";
441
+ }
434
442
  const response = await this.rpc.proxy.request.bridge({
435
443
  functionName: "create_file_or_directory",
436
- payload: request,
444
+ payload: {
445
+ path: request.path,
446
+ type: request.type,
447
+ name: request.name,
448
+ contents,
449
+ },
437
450
  });
438
451
  return response as FileCreateResponse;
439
452
  };
@@ -10,3 +10,17 @@ export function isWasm(): boolean {
10
10
  document.querySelector("marimo-wasm") !== null
11
11
  );
12
12
  }
13
+
14
+ const DUCKDB_USAGE_PATTERN =
15
+ /(^|\n)\s*(?:import\s+[^\n#]*\bduckdb\b|from\s+duckdb\b|[^\n#]*\bduckdb\s*\.)/;
16
+
17
+ export function shouldLoadDuckDBPackages(
18
+ code: string,
19
+ foundPackages?: ReadonlySet<string>,
20
+ ): boolean {
21
+ return (
22
+ code.includes("mo.sql") ||
23
+ DUCKDB_USAGE_PATTERN.test(code) ||
24
+ foundPackages?.has("duckdb") === true
25
+ );
26
+ }
@@ -9,6 +9,7 @@ import { WasmFileSystem } from "./fs";
9
9
  import { getMarimoWheel } from "./getMarimoWheel";
10
10
  import { t } from "./tracer";
11
11
  import type { SerializedBridge, WasmController } from "./types";
12
+ import { shouldLoadDuckDBPackages } from "../utils";
12
13
 
13
14
  const MAKE_SNAPSHOT = false;
14
15
 
@@ -163,8 +164,8 @@ export class DefaultWasmController implements WasmController {
163
164
  private async loadNotebookDeps(code: string, foundPackages: Set<string>) {
164
165
  const pyodide = this.requirePyodide;
165
166
 
166
- if (code.includes("mo.sql")) {
167
- // We need pandas and duckdb for mo.sql
167
+ if (shouldLoadDuckDBPackages(code, foundPackages)) {
168
+ // We need pandas and duckdb for mo.sql and for remote duckdb sources
168
169
  code = `import pandas\n${code}`;
169
170
  code = `import duckdb\n${code}`;
170
171
  code = `import sqlglot\n${code}`;
@@ -34,6 +34,7 @@ import type {
34
34
  SerializedBridge,
35
35
  WasmController,
36
36
  } from "./types";
37
+ import { shouldLoadDuckDBPackages } from "../utils";
37
38
 
38
39
  /**
39
40
  * Web worker responsible for running the notebook.
@@ -141,8 +142,8 @@ const requestHandler = createRPCRequestHandler({
141
142
  const span = t.startSpan("loadPackages");
142
143
  await pyodideReadyPromise; // Make sure loading is done
143
144
 
144
- if (code.includes("mo.sql")) {
145
- // Add pandas and duckdb to the code
145
+ if (shouldLoadDuckDBPackages(code)) {
146
+ // Add pandas and duckdb to the code for mo.sql and for remote duckdb sources
146
147
  code = `import pandas\n${code}`;
147
148
  code = `import duckdb\n${code}`;
148
149
  code = `import sqlglot\n${code}`;
@@ -0,0 +1,155 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ // @vitest-environment jsdom
3
+
4
+ import { act, renderHook } from "@testing-library/react";
5
+ import { createStore, Provider as JotaiProvider } from "jotai";
6
+ import type React from "react";
7
+ import { ErrorBoundary } from "react-error-boundary";
8
+ import { beforeEach, describe, expect, it, vi } from "vitest";
9
+
10
+ vi.mock("@/core/websocket/useWebSocket", async () => {
11
+ const actual =
12
+ await vi.importActual<typeof import("../useWebSocket")>("../useWebSocket");
13
+ return {
14
+ ...actual,
15
+ useConnectionTransport: vi.fn(),
16
+ };
17
+ });
18
+
19
+ vi.mock("@/core/runtime/config", async () => {
20
+ const actual = await vi.importActual<typeof import("@/core/runtime/config")>(
21
+ "@/core/runtime/config",
22
+ );
23
+ return {
24
+ ...actual,
25
+ useRuntimeManager: vi.fn(),
26
+ };
27
+ });
28
+
29
+ import { useRuntimeManager } from "@/core/runtime/config";
30
+ import { connectionAtom } from "../../network/connection";
31
+ import type { SessionId } from "../../kernel/session";
32
+ import { WebSocketClosedReason, WebSocketState } from "../types";
33
+ import { useMarimoKernelConnection } from "../useMarimoKernelConnection";
34
+ import { useConnectionTransport } from "../useWebSocket";
35
+
36
+ interface MockTransport {
37
+ readyState: 0 | 1 | 2 | 3;
38
+ retryCount: number;
39
+ reconnect: ReturnType<typeof vi.fn>;
40
+ close: ReturnType<typeof vi.fn>;
41
+ send: ReturnType<typeof vi.fn>;
42
+ addEventListener: ReturnType<typeof vi.fn>;
43
+ removeEventListener: ReturnType<typeof vi.fn>;
44
+ }
45
+
46
+ function makeTransport(
47
+ readyState: 0 | 1 | 2 | 3 = WebSocket.CLOSED,
48
+ ): MockTransport {
49
+ return {
50
+ readyState,
51
+ retryCount: 0,
52
+ reconnect: vi.fn(),
53
+ close: vi.fn(),
54
+ send: vi.fn(),
55
+ addEventListener: vi.fn(),
56
+ removeEventListener: vi.fn(),
57
+ };
58
+ }
59
+
60
+ function makeRuntimeManager(isHealthy = vi.fn().mockResolvedValue(true)) {
61
+ return {
62
+ isHealthy,
63
+ getWsURL: () => new URL("ws://localhost/ws"),
64
+ waitForHealthy: vi.fn().mockResolvedValue(undefined),
65
+ isSameOrigin: true,
66
+ };
67
+ }
68
+
69
+ describe("useMarimoKernelConnection.reconnect()", () => {
70
+ let transport: MockTransport;
71
+ let isHealthy: ReturnType<typeof vi.fn>;
72
+ let store: ReturnType<typeof createStore>;
73
+
74
+ beforeEach(() => {
75
+ transport = makeTransport(WebSocket.CLOSED);
76
+ isHealthy = vi.fn().mockResolvedValue(true);
77
+ store = createStore();
78
+ store.set(connectionAtom, {
79
+ state: WebSocketState.CLOSED,
80
+ code: WebSocketClosedReason.KERNEL_DISCONNECTED,
81
+ reason: "kernel not found",
82
+ });
83
+ vi.mocked(useConnectionTransport).mockReturnValue(transport);
84
+ vi.mocked(useRuntimeManager).mockReturnValue(
85
+ makeRuntimeManager(isHealthy) as unknown as ReturnType<
86
+ typeof useRuntimeManager
87
+ >,
88
+ );
89
+ });
90
+
91
+ function renderUseHook() {
92
+ const wrapper: React.FC<React.PropsWithChildren> = ({ children }) => (
93
+ <JotaiProvider store={store}>
94
+ <ErrorBoundary fallback={null}>{children}</ErrorBoundary>
95
+ </JotaiProvider>
96
+ );
97
+ return renderHook(
98
+ () =>
99
+ useMarimoKernelConnection({
100
+ sessionId: "test-session" as SessionId,
101
+ autoInstantiate: false,
102
+ setCells: () => {},
103
+ }),
104
+ { wrapper },
105
+ );
106
+ }
107
+
108
+ it("is a no-op when the transport is already OPEN", async () => {
109
+ transport.readyState = WebSocket.OPEN;
110
+ const { result } = renderUseHook();
111
+ await act(async () => {
112
+ await result.current.reconnect();
113
+ });
114
+ expect(isHealthy).not.toHaveBeenCalled();
115
+ expect(transport.reconnect).not.toHaveBeenCalled();
116
+ });
117
+
118
+ it("is a no-op when the transport is already CONNECTING", async () => {
119
+ transport.readyState = WebSocket.CONNECTING;
120
+ const { result } = renderUseHook();
121
+ await act(async () => {
122
+ await result.current.reconnect();
123
+ });
124
+ expect(isHealthy).not.toHaveBeenCalled();
125
+ expect(transport.reconnect).not.toHaveBeenCalled();
126
+ });
127
+
128
+ it("probes /health and reconnects when the runtime is healthy", async () => {
129
+ isHealthy.mockResolvedValue(true);
130
+ const { result } = renderUseHook();
131
+ await act(async () => {
132
+ await result.current.reconnect();
133
+ });
134
+ expect(isHealthy).toHaveBeenCalledOnce();
135
+ expect(transport.reconnect).toHaveBeenCalledOnce();
136
+ expect(store.get(connectionAtom)).toEqual({
137
+ state: WebSocketState.CONNECTING,
138
+ });
139
+ });
140
+
141
+ it("transitions to CLOSED and does not call ws.reconnect when the probe fails", async () => {
142
+ isHealthy.mockResolvedValue(false);
143
+ const { result } = renderUseHook();
144
+ await act(async () => {
145
+ await result.current.reconnect();
146
+ });
147
+ expect(isHealthy).toHaveBeenCalledOnce();
148
+ expect(transport.reconnect).not.toHaveBeenCalled();
149
+ expect(store.get(connectionAtom)).toEqual({
150
+ state: WebSocketState.CLOSED,
151
+ code: WebSocketClosedReason.KERNEL_DISCONNECTED,
152
+ reason: "kernel not found",
153
+ });
154
+ });
155
+ });
@@ -0,0 +1,137 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { afterEach, describe, expect, it, vi } from "vitest";
4
+ import { Logger } from "@/utils/Logger";
5
+ import { WebSocketClosedReason, WebSocketState } from "../types";
6
+ import { classifyCloseEvent } from "../useMarimoKernelConnection";
7
+ import { MAX_RETRIES } from "../useWebSocket";
8
+
9
+ function classify(
10
+ reason: string | undefined,
11
+ retryCount = 0,
12
+ maxRetries = MAX_RETRIES,
13
+ ) {
14
+ return classifyCloseEvent({ reason }, { retryCount, maxRetries });
15
+ }
16
+
17
+ describe("classifyCloseEvent", () => {
18
+ describe("transient closes (default branch)", () => {
19
+ it("retries when retryCount < maxRetries", () => {
20
+ const decision = classify(undefined, 0);
21
+ expect(decision.kind).toBe("retry");
22
+ expect(decision.status).toEqual({ state: WebSocketState.CONNECTING });
23
+ });
24
+
25
+ it("retries on each intermediate close event during a retry storm", () => {
26
+ for (let n = 0; n < MAX_RETRIES; n++) {
27
+ const decision = classify(undefined, n);
28
+ expect(decision.kind).toBe("retry");
29
+ expect(decision.status).toEqual({ state: WebSocketState.CONNECTING });
30
+ }
31
+ });
32
+
33
+ it("transitions to CLOSED when retryCount reaches maxRetries", () => {
34
+ const decision = classify(undefined, MAX_RETRIES);
35
+ expect(decision.kind).toBe("gave-up");
36
+ expect(decision.status).toEqual({
37
+ state: WebSocketState.CLOSED,
38
+ code: WebSocketClosedReason.KERNEL_DISCONNECTED,
39
+ reason: "kernel not found",
40
+ });
41
+ });
42
+
43
+ it("transitions to CLOSED when retryCount exceeds maxRetries", () => {
44
+ const decision = classify(undefined, MAX_RETRIES + 5);
45
+ expect(decision.kind).toBe("gave-up");
46
+ });
47
+
48
+ it("treats unknown reason strings as transient and logs a warning", () => {
49
+ const logger = vi.spyOn(Logger, "warn").mockImplementation(() => {});
50
+ const decision = classify("something-else", 3);
51
+ expect(decision.kind).toBe("retry");
52
+ expect(logger).toHaveBeenCalled();
53
+ logger.mockRestore();
54
+ });
55
+ });
56
+
57
+ afterEach(() => {
58
+ vi.restoreAllMocks();
59
+ });
60
+
61
+ describe("terminal closes (server-initiated)", () => {
62
+ it("MARIMO_ALREADY_CONNECTED → terminal + closeTransport, with takeover", () => {
63
+ const decision = classify("MARIMO_ALREADY_CONNECTED", 0);
64
+ expect(decision.kind).toBe("terminal");
65
+ expect(decision.status).toMatchObject({
66
+ state: WebSocketState.CLOSED,
67
+ code: WebSocketClosedReason.ALREADY_RUNNING,
68
+ canTakeover: true,
69
+ });
70
+ if (decision.kind === "terminal") {
71
+ expect(decision.closeTransport).toBe(true);
72
+ }
73
+ });
74
+
75
+ it.each([
76
+ "MARIMO_WRONG_KERNEL_ID",
77
+ "MARIMO_NO_FILE_KEY",
78
+ "MARIMO_NO_SESSION_ID",
79
+ "MARIMO_NO_SESSION",
80
+ "MARIMO_SHUTDOWN",
81
+ ])("%s → terminal with KERNEL_DISCONNECTED, closes transport", (reason) => {
82
+ const decision = classify(reason, 0);
83
+ expect(decision.kind).toBe("terminal");
84
+ expect(decision.status).toMatchObject({
85
+ state: WebSocketState.CLOSED,
86
+ code: WebSocketClosedReason.KERNEL_DISCONNECTED,
87
+ });
88
+ if (decision.kind === "terminal") {
89
+ expect(decision.closeTransport).toBe(true);
90
+ }
91
+ });
92
+
93
+ it("MARIMO_MALFORMED_QUERY → terminal but does NOT close transport", () => {
94
+ const decision = classify("MARIMO_MALFORMED_QUERY", 0);
95
+ expect(decision.kind).toBe("terminal");
96
+ expect(decision.status).toMatchObject({
97
+ state: WebSocketState.CLOSED,
98
+ code: WebSocketClosedReason.MALFORMED_QUERY,
99
+ });
100
+ if (decision.kind === "terminal") {
101
+ expect(decision.closeTransport).toBe(false);
102
+ }
103
+ });
104
+
105
+ it("MARIMO_KERNEL_STARTUP_ERROR → terminal + closeTransport", () => {
106
+ const decision = classify("MARIMO_KERNEL_STARTUP_ERROR", 0);
107
+ expect(decision.kind).toBe("terminal");
108
+ expect(decision.status).toMatchObject({
109
+ state: WebSocketState.CLOSED,
110
+ code: WebSocketClosedReason.KERNEL_STARTUP_ERROR,
111
+ });
112
+ if (decision.kind === "terminal") {
113
+ expect(decision.closeTransport).toBe(true);
114
+ }
115
+ });
116
+
117
+ it("terminal closes ignore retryCount entirely", () => {
118
+ const decision = classify("MARIMO_SHUTDOWN", 99);
119
+ expect(decision.kind).toBe("terminal");
120
+ });
121
+ });
122
+
123
+ describe("retry budget exhaustion", () => {
124
+ it("yields retry on attempts 1..maxRetries-1 and gave-up on the final close", () => {
125
+ const states: string[] = [];
126
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
127
+ states.push(classify(undefined, attempt - 1).kind);
128
+ }
129
+ states.push(classify(undefined, MAX_RETRIES).kind);
130
+
131
+ expect(states).toEqual([
132
+ ...Array.from({ length: MAX_RETRIES }, () => "retry"),
133
+ "gave-up",
134
+ ]);
135
+ });
136
+ });
137
+ });
@@ -43,6 +43,8 @@ export class BasicTransport implements IConnectionTransport {
43
43
  return WebSocket.OPEN;
44
44
  }
45
45
 
46
+ readonly retryCount = 0;
47
+
46
48
  reconnect(code?: number | undefined, reason?: string | undefined): void {
47
49
  this.close();
48
50
  this.connect();
@@ -25,6 +25,7 @@ export interface IConnectionTransport {
25
25
  callback: ConnectionTransportCallback<T>,
26
26
  ): void;
27
27
  readyState: WebSocket["readyState"];
28
+ readonly retryCount: number;
28
29
  }
29
30
 
30
31
  export class ConnectionSubscriptions {