@marimo-team/islands 0.23.7-dev2 → 0.23.7-dev22

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 (511) 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-Den2QMz-.js} +621 -235
  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-BZocwq8O.js → code-visibility-geywCqhR.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 +1181 -1144
  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-DJ1NyBGw.js → mermaid-DO-Daq7u.js} +72 -69
  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-CP9dWJEm.js → reveal-component-BYYXwJoo.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 +4 -4
  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/controls/Controls.tsx +3 -1
  235. package/src/components/editor/file-tree/requesting-tree.tsx +27 -25
  236. package/src/components/editor/file-tree/upload.tsx +23 -24
  237. package/src/components/editor/header/__tests__/status.test.tsx +108 -0
  238. package/src/components/editor/header/status.tsx +44 -10
  239. package/src/components/editor/navigation/__tests__/clipboard.test.ts +106 -0
  240. package/src/components/editor/navigation/__tests__/navigation.test.ts +70 -0
  241. package/src/components/editor/navigation/clipboard.ts +99 -25
  242. package/src/components/editor/navigation/navigation.ts +15 -1
  243. package/src/components/editor/notebook-cell.tsx +3 -0
  244. package/src/components/pages/run-page.tsx +4 -1
  245. package/src/core/ai/tools/__tests__/registry.test.ts +10 -12
  246. package/src/core/ai/tools/registry.ts +9 -5
  247. package/src/core/cells/__tests__/cells.test.ts +187 -0
  248. package/src/core/cells/__tests__/pending-cut-service.test.tsx +123 -0
  249. package/src/core/cells/cells.ts +102 -17
  250. package/src/core/cells/document-changes.ts +6 -1
  251. package/src/core/cells/pending-cut-service.ts +55 -0
  252. package/src/core/cells/utils.ts +11 -0
  253. package/src/core/codemirror/cells/extensions.ts +10 -0
  254. package/src/core/codemirror/markdown/__tests__/commands.test.ts +3 -3
  255. package/src/core/codemirror/markdown/commands.ts +1 -2
  256. package/src/core/edit-app.tsx +2 -1
  257. package/src/core/hotkeys/hotkeys.ts +5 -0
  258. package/src/core/islands/worker/worker.tsx +3 -2
  259. package/src/core/network/requests-network.ts +21 -3
  260. package/src/core/network/types.ts +12 -1
  261. package/src/core/run-app.tsx +2 -1
  262. package/src/core/wasm/__tests__/utils.test.ts +34 -0
  263. package/src/core/wasm/bridge.ts +14 -1
  264. package/src/core/wasm/utils.ts +14 -0
  265. package/src/core/wasm/worker/bootstrap.ts +3 -2
  266. package/src/core/wasm/worker/worker.ts +3 -2
  267. package/src/core/websocket/__tests__/useMarimoKernelConnection.hook.test.tsx +155 -0
  268. package/src/core/websocket/__tests__/useMarimoKernelConnection.test.ts +137 -0
  269. package/src/core/websocket/transports/basic.ts +2 -0
  270. package/src/core/websocket/transports/transport.ts +1 -0
  271. package/src/core/websocket/useMarimoKernelConnection.tsx +130 -55
  272. package/src/core/websocket/useWebSocket.tsx +5 -2
  273. package/src/css/app/Cell.css +10 -0
  274. package/src/plugins/core/__test__/sanitize.test.ts +8 -0
  275. package/src/plugins/impl/DataTablePlugin.tsx +12 -0
  276. package/src/plugins/impl/TabsPlugin.tsx +35 -7
  277. package/src/plugins/impl/__tests__/TabsPlugin.test.tsx +154 -0
  278. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +6 -0
  279. package/src/plugins/impl/vega/resolve-data.ts +8 -1
  280. package/src/plugins/layout/__test__/MermaidPlugin.test.ts +50 -0
  281. package/src/plugins/layout/mermaid/MermaidPlugin.tsx +11 -1
  282. package/src/plugins/layout/mermaid/mermaid.tsx +7 -3
  283. package/src/utils/__tests__/id-tree.test.ts +71 -0
  284. package/src/utils/__tests__/semaphore.test.ts +218 -0
  285. package/src/utils/fileToBase64.ts +8 -7
  286. package/src/utils/id-tree.tsx +89 -0
  287. package/src/utils/semaphore.ts +88 -0
  288. package/dist/apl-BKoVld9y.js +0 -4
  289. package/dist/asciiarmor-DQrKIjoo.js +0 -4
  290. package/dist/asn1-BZvnj0dq.js +0 -4
  291. package/dist/assets/__vite-browser-external-rrUYDKRl.js +0 -1
  292. package/dist/assets/worker-Bfy15ViQ.js +0 -73
  293. package/dist/brainfuck-D558nlUv.js +0 -4
  294. package/dist/classDiagram-2ON5EDUG-CBHMR6ZU.js +0 -30
  295. package/dist/classDiagram-v2-WZHVMYZB-BsUtUGM_.js +0 -30
  296. package/dist/clojure-Cq8mTSrE.js +0 -4
  297. package/dist/cmake-D8HCovWK.js +0 -4
  298. package/dist/cobol-UolN-9iU.js +0 -4
  299. package/dist/coffeescript-VdNuWrt5.js +0 -4
  300. package/dist/commonlisp-ALX7fpDc.js +0 -4
  301. package/dist/crystal-PbyO9Q_s.js +0 -4
  302. package/dist/css-DFklJkr_.js +0 -4
  303. package/dist/cypher-BifNeYlv.js +0 -4
  304. package/dist/d-BA-JP4PJ.js +0 -4
  305. package/dist/diff-CtkDpav4.js +0 -4
  306. package/dist/dist-BuBwsFva.js +0 -5
  307. package/dist/dist-BzmEQ9u7.js +0 -5
  308. package/dist/dist-Cih01ssx.js +0 -5
  309. package/dist/dist-CqfONiY9.js +0 -5
  310. package/dist/dist-D0iD0Fi9.js +0 -5
  311. package/dist/dist-DtNLXm8d.js +0 -5
  312. package/dist/dtd-DW3_UFEG.js +0 -4
  313. package/dist/dylan-pDhodO2N.js +0 -4
  314. package/dist/ecl-BJT8-YD7.js +0 -4
  315. package/dist/eiffel-Dmns-9vS.js +0 -4
  316. package/dist/elm-Da4sO4Bz.js +0 -4
  317. package/dist/erlang-C-zBsDi7.js +0 -4
  318. package/dist/factor-4xPWlWB5.js +0 -4
  319. package/dist/forth-l-c75zSd.js +0 -4
  320. package/dist/fortran-DIujSODW.js +0 -4
  321. package/dist/gas-CXnG5g_b.js +0 -4
  322. package/dist/gherkin-VPeqd4-X.js +0 -4
  323. package/dist/groovy-CphhZQgg.js +0 -4
  324. package/dist/haskell-CCvlS5Iq.js +0 -4
  325. package/dist/haxe-C_bi66fP.js +0 -5
  326. package/dist/idl-1DcP4Dm8.js +0 -4
  327. package/dist/javascript-DUIGhBvO.js +0 -4
  328. package/dist/julia-Cs2G4PQi.js +0 -4
  329. package/dist/livescript-DMtVFaAN.js +0 -4
  330. package/dist/lua-BAoLtdJg.js +0 -4
  331. package/dist/mathematica-C_NoFtbo.js +0 -4
  332. package/dist/mbox-DcFJFYrH.js +0 -4
  333. package/dist/mirc-71dccf_u.js +0 -4
  334. package/dist/mllike-CWcOFVDq.js +0 -6
  335. package/dist/modelica-Ape2VXxx.js +0 -4
  336. package/dist/mscgen-Cc6TwbSN.js +0 -6
  337. package/dist/mumps-h-ZbdkJ9.js +0 -4
  338. package/dist/nsis-C0p3m7JW.js +0 -4
  339. package/dist/ntriples-c9lEeT5w.js +0 -4
  340. package/dist/octave-DzEgB_74.js +0 -4
  341. package/dist/oz-CAxvHkyQ.js +0 -4
  342. package/dist/pascal-BJzu1sgP.js +0 -4
  343. package/dist/perl--IrOzZ2Z.js +0 -4
  344. package/dist/pig-CiBKKNhC.js +0 -4
  345. package/dist/powershell-KY0j6Qop.js +0 -4
  346. package/dist/properties-BW8q3ziV.js +0 -4
  347. package/dist/protobuf-BGaeuTGV.js +0 -4
  348. package/dist/pug-DjOKK-4J.js +0 -4
  349. package/dist/puppet-DWm2o6zX.js +0 -4
  350. package/dist/python-Bp2gezZy.js +0 -4
  351. package/dist/q-DljPshos.js +0 -4
  352. package/dist/r-BajPMnEu.js +0 -4
  353. package/dist/rpm-BKx-ZZ62.js +0 -5
  354. package/dist/ruby-DJq_HNKc.js +0 -4
  355. package/dist/sas-WANvpcOU.js +0 -4
  356. package/dist/scheme-CliBbhGF.js +0 -4
  357. package/dist/shell-BwhrNUvM.js +0 -4
  358. package/dist/sieve-BIVePvMp.js +0 -4
  359. package/dist/smalltalk-D6G48JmY.js +0 -4
  360. package/dist/sparql-jjc3BmEP.js +0 -4
  361. package/dist/stateDiagram-v2-4FDKWEC3-Cv9Av10H.js +0 -29
  362. package/dist/stylus-WPBPQ4PE.js +0 -4
  363. package/dist/swift-O1Qy6iCm.js +0 -4
  364. package/dist/tcl-BAFdhvsi.js +0 -4
  365. package/dist/textile-DFuzhNLG.js +0 -4
  366. package/dist/toml-DRSTeely.js +0 -4
  367. package/dist/troff-B_ZjwBW0.js +0 -4
  368. package/dist/ttcn-CAyiB3ic.js +0 -4
  369. package/dist/ttcn-cfg-BS5_BGBJ.js +0 -4
  370. package/dist/turtle-CUBEDy3E.js +0 -4
  371. package/dist/vb-DY9S6-U2.js +0 -4
  372. package/dist/vbscript-gaHC39Jq.js +0 -4
  373. package/dist/velocity-TfCOtJZ_.js +0 -4
  374. package/dist/verilog-c2JOX8mv.js +0 -4
  375. package/dist/vhdl-dHBirRiO.js +0 -4
  376. package/dist/webidl-Bauj-i07.js +0 -4
  377. package/dist/xquery-CtaEAOt8.js +0 -4
  378. package/dist/yacas-BZ85agQP.js +0 -4
  379. package/dist/z80-hCgR-L4U.js +0 -4
  380. package/src/components/chat/tool-call-accordion.tsx +0 -247
  381. /package/dist/{ImageComparisonComponent-DaocPIse.js → ImageComparisonComponent-CNHIsPDj.js} +0 -0
  382. /package/dist/{Plot-PIeIvFnD.js → Plot-4wn-lMVn.js} +0 -0
  383. /package/dist/{apl-Dt8GMXYg.js → apl-BCgCq9iM.js} +0 -0
  384. /package/dist/{array-B-MVxRIF.js → array-tvvEqPy7.js} +0 -0
  385. /package/dist/{asciiarmor-CitDQ85h.js → asciiarmor-BtqU-KJQ.js} +0 -0
  386. /package/dist/{asn1-abrf9SMK.js → asn1-Dmb-dTMx.js} +0 -0
  387. /package/dist/{asterisk-BUZwqih-.js → asterisk-DaVJJDnV.js} +0 -0
  388. /package/dist/{brainfuck-BL-Boof0.js → brainfuck-C1HoZKlE.js} +0 -0
  389. /package/dist/{chunk-4F5CHEZ2-C6tO9vjs.js → chunk-4F5CHEZ2-BZq7Kom7.js} +0 -0
  390. /package/dist/{chunk-B2363JML-Ds8wZXyP.js → chunk-B2363JML-D9-XOau1.js} +0 -0
  391. /package/dist/{chunk-DR5Q36YT-CP69aZS_.js → chunk-DR5Q36YT-BflwErH1.js} +0 -0
  392. /package/dist/{chunk-FRFDVMJY-BgQv1HBE.js → chunk-FRFDVMJY-BSBUAX7r.js} +0 -0
  393. /package/dist/{chunk-PL6DKKU2-DHfTUHy8.js → chunk-PL6DKKU2-B0MTXvyc.js} +0 -0
  394. /package/dist/{chunk-SJTYNZTY-Diciw4sx.js → chunk-SJTYNZTY-CEG4F0pB.js} +0 -0
  395. /package/dist/{chunk-TQ3KTPDO-CQfP9npd.js → chunk-TQ3KTPDO-DiCtqVSi.js} +0 -0
  396. /package/dist/{chunk-UMXZTB3W-MSKeGL7W.js → chunk-UMXZTB3W-97iS1iEl.js} +0 -0
  397. /package/dist/{click-outside-container-BZgN7xS_.js → click-outside-container-BDd67_1U.js} +0 -0
  398. /package/dist/{clike-RWg7anhx.js → clike-CdT0yHjt.js} +0 -0
  399. /package/dist/{clojure-DaojKHow.js → clojure-CdyrCpUv.js} +0 -0
  400. /package/dist/{cmake-DN-_v0XE.js → cmake-BFlPxym7.js} +0 -0
  401. /package/dist/{cobol-C3VpMyux.js → cobol-CcJXewp8.js} +0 -0
  402. /package/dist/{coffeescript-DIkz3Tbt.js → coffeescript-DnKuIKRo.js} +0 -0
  403. /package/dist/{colors-Cn2p_FA3.js → colors-CQAOa8cK.js} +0 -0
  404. /package/dist/{common-keywords-hbLeU7VU.js → common-keywords-FBrXPTcz.js} +0 -0
  405. /package/dist/{commonlisp-CB1boOiP.js → commonlisp-B-kok83Z.js} +0 -0
  406. /package/dist/{crystal-DI2oCml6.js → crystal-FYRYjI1I.js} +0 -0
  407. /package/dist/{css-BdEVwQDV.js → css-B45lc2V3.js} +0 -0
  408. /package/dist/{cypher-BNHToqxU.js → cypher-DZMLyVY_.js} +0 -0
  409. /package/dist/{cytoscape.esm-WbbDoCfu.js → cytoscape.esm-ayF70frT.js} +0 -0
  410. /package/dist/{d-D7we7I1b.js → d-x-VVT4o9.js} +0 -0
  411. /package/dist/{diff-Cia6fzjN.js → diff-Dxe2mpXk.js} +0 -0
  412. /package/dist/{dist-BK-3fF4P.js → dist-B4LJpMEg.js} +0 -0
  413. /package/dist/{dist-CxdUraQr.js → dist-B507mf_I.js} +0 -0
  414. /package/dist/{dist-C89sHDXk.js → dist-BGdYVvOu.js} +0 -0
  415. /package/dist/{dist-DquyVv5H.js → dist-BNyrZfqT.js} +0 -0
  416. /package/dist/{dist-Zn0KNbo9.js → dist-Bc5pmZIw.js} +0 -0
  417. /package/dist/{dist-C-J0pt5p.js → dist-BvCfQQQE.js} +0 -0
  418. /package/dist/{dist-D9r7Cmw7.js → dist-C2ej4eOH.js} +0 -0
  419. /package/dist/{dist-HVuryI1a.js → dist-C34oIrQ9.js} +0 -0
  420. /package/dist/{dist-CGLzXdrt.js → dist-CDFZi-QD.js} +0 -0
  421. /package/dist/{dist-C9fmTOin.js → dist-CYEylvZA.js} +0 -0
  422. /package/dist/{dist-DadjmS-4.js → dist-DJ6zJQZ4.js} +0 -0
  423. /package/dist/{dist-CtCY55Jf.js → dist-Dh3wkoyH.js} +0 -0
  424. /package/dist/{dist-C474qFoq.js → dist-Dhk6FMb0.js} +0 -0
  425. /package/dist/{dist-DZjQ_MBo.js → dist-KnujRhFL.js} +0 -0
  426. /package/dist/{dist-CinA9Enb.js → dist-WdPUFc56.js} +0 -0
  427. /package/dist/{dist-DBLeRrPp.js → dist-t_qL7eB8.js} +0 -0
  428. /package/dist/{dist-CyFFzJTb.js → dist-usPCDYx8.js} +0 -0
  429. /package/dist/{dtd-H4Hubdwp.js → dtd-C9VM_Wfu.js} +0 -0
  430. /package/dist/{duckdb-keywords-CZ_ZTscu.js → duckdb-keywords-CvJhR_Yd.js} +0 -0
  431. /package/dist/{dylan-fVO6rnq3.js → dylan-DTSnEIFO.js} +0 -0
  432. /package/dist/{ebnf-WEXPLEWb.js → ebnf-2D4Ctp3y.js} +0 -0
  433. /package/dist/{ecl-B94VPjNR.js → ecl-N04ptnRK.js} +0 -0
  434. /package/dist/{eiffel-C_R6TusS.js → eiffel-Dd8rpqr_.js} +0 -0
  435. /package/dist/{elm-DzCHbO2g.js → elm-GT2E866W.js} +0 -0
  436. /package/dist/{erlang-BGNkx6JU.js → erlang-Cf0Bp5pY.js} +0 -0
  437. /package/dist/{esm-Bb_hbWan.js → esm-BaaaPNGl.js} +0 -0
  438. /package/dist/{fcl-B_Gv5Jfx.js → fcl-Ccj8Z5Xd.js} +0 -0
  439. /package/dist/{forth-Bybw0cJ7.js → forth-wd_XzGTg.js} +0 -0
  440. /package/dist/{fortran-C6PoCLkI.js → fortran-DcwUTZFe.js} +0 -0
  441. /package/dist/{gas-BBlhenj4.js → gas-DeALIER3.js} +0 -0
  442. /package/dist/{gherkin-NXtNG85X.js → gherkin-CKTqaJNX.js} +0 -0
  443. /package/dist/{groovy-BoFYK9xM.js → groovy-Bwdp_d8D.js} +0 -0
  444. /package/dist/{haskell-BtBdvQ1n.js → haskell-DCdCcPLK.js} +0 -0
  445. /package/dist/{haxe-D--o6dr0.js → haxe-DAyktQWJ.js} +0 -0
  446. /package/dist/{http-Dc2fv19V.js → http-_DVAYWoR.js} +0 -0
  447. /package/dist/{idl-AqTq5l7e.js → idl-CBuZiRYu.js} +0 -0
  448. /package/dist/{init-D-g0ONX1.js → init-uv0kkh4g.js} +0 -0
  449. /package/dist/{javascript-DvwNVye9.js → javascript-DzigE11c.js} +0 -0
  450. /package/dist/{julia-DoKiagZC.js → julia-PwfB-0Cm.js} +0 -0
  451. /package/dist/{katex-B7pMJpE0.js → katex-C_XRmjAP.js} +0 -0
  452. /package/dist/{livescript-DxBZMiWB.js → livescript-BJLz1EbT.js} +0 -0
  453. /package/dist/{lua-DmS_0NTu.js → lua-ZC-XC2jf.js} +0 -0
  454. /package/dist/{math-BYK36kWZ.js → math-DFcdCCU8.js} +0 -0
  455. /package/dist/{mathematica-ChlDFeIC.js → mathematica-DCYMx6qB.js} +0 -0
  456. /package/dist/{mbox-CguZuODr.js → mbox-OxMK_9XI.js} +0 -0
  457. /package/dist/{mirc-CFtY8dqz.js → mirc-nJVyhA0H.js} +0 -0
  458. /package/dist/{mllike-C0EJrEOk.js → mllike-DRO89bsU.js} +0 -0
  459. /package/dist/{modelica-C1kO1nfS.js → modelica-Don3E6ZD.js} +0 -0
  460. /package/dist/{mscgen-DEYdr7AY.js → mscgen-DJfqD3bN.js} +0 -0
  461. /package/dist/{mumps-B3NVJs2V.js → mumps-SjGTvDYL.js} +0 -0
  462. /package/dist/{nginx-ComVAAGN.js → nginx-DasThI7R.js} +0 -0
  463. /package/dist/{node-sql-parser-DNGGJ-Rw.js → node-sql-parser-B8nBD36q.js} +0 -0
  464. /package/dist/{ntriples-DHol9X9H.js → ntriples-CNBKRl3I.js} +0 -0
  465. /package/dist/{octave-CYGz0bfo.js → octave-DdeVHNlx.js} +0 -0
  466. /package/dist/{oz-kPxb2ni5.js → oz-CcKSoNvN.js} +0 -0
  467. /package/dist/{pascal-bZ0yrJKy.js → pascal-6leftwNj.js} +0 -0
  468. /package/dist/{path-Du6n3sOU.js → path-BGaWgPKg.js} +0 -0
  469. /package/dist/{perl-z4hvqyqz.js → perl-BhJIwWzN.js} +0 -0
  470. /package/dist/{pig-DZO8QDF9.js → pig-r-xDHqRf.js} +0 -0
  471. /package/dist/{powershell-BSuaDQEC.js → powershell-D-BELeNi.js} +0 -0
  472. /package/dist/{properties-BXhGLlIx.js → properties-CnuDhbll.js} +0 -0
  473. /package/dist/{protobuf-DM6iybWV.js → protobuf-CO8RBhvX.js} +0 -0
  474. /package/dist/{puppet-Bn05sQT8.js → puppet-NmXHjLy8.js} +0 -0
  475. /package/dist/{python-Cvnhm0g7.js → python-DAQXi720.js} +0 -0
  476. /package/dist/{q-B9V8hzex.js → q-DlikXfV0.js} +0 -0
  477. /package/dist/{r-Cf0gFqmq.js → r-CuohilwT.js} +0 -0
  478. /package/dist/{rpm-D-LMkTV1.js → rpm-0Pjwp0Pb.js} +0 -0
  479. /package/dist/{ruby-DeuPikpK.js → ruby-Dq8NJTDG.js} +0 -0
  480. /package/dist/{sas-C9tjgAo9.js → sas-CuwonyVP.js} +0 -0
  481. /package/dist/{scheme-D1_bUF0G.js → scheme-CYU-RRIf.js} +0 -0
  482. /package/dist/{shell-CJBmnks3.js → shell-COPmX2qE.js} +0 -0
  483. /package/dist/{sieve-1fSV75CF.js → sieve-B_3zyLne.js} +0 -0
  484. /package/dist/{simple-mode-B90Wdavj.js → simple-mode-DSBniks8.js} +0 -0
  485. /package/dist/{smalltalk-sZNPD0HO.js → smalltalk-DRft7iPv.js} +0 -0
  486. /package/dist/{solr-DTkyqJ-Z.js → solr-RZ9uTl59.js} +0 -0
  487. /package/dist/{sparql-oHc1nm77.js → sparql-CN6qj55H.js} +0 -0
  488. /package/dist/{spreadsheet-CER0raqY.js → spreadsheet-BNNUNXA2.js} +0 -0
  489. /package/dist/{sql-ByOoEONQ.js → sql-B4x8IkwU.js} +0 -0
  490. /package/dist/{stylus-KzkX6zRB.js → stylus-Bn_ZjOQ3.js} +0 -0
  491. /package/dist/{swift-DqVxZvKo.js → swift-BLUJhMbz.js} +0 -0
  492. /package/dist/{tcl-BtWSwXfA.js → tcl-C86fxecl.js} +0 -0
  493. /package/dist/{textile-CWDbn9Ql.js → textile-DmHh2rsK.js} +0 -0
  494. /package/dist/{tiddlywiki-Cr9xyOY1.js → tiddlywiki-DI0mF2WJ.js} +0 -0
  495. /package/dist/{tiki-D5JONyfZ.js → tiki-2HU6XLLn.js} +0 -0
  496. /package/dist/{timer-D7JVdX9U.js → timer-YZl28NYN.js} +0 -0
  497. /package/dist/{toml-BfehlgmL.js → toml-GWANRNAD.js} +0 -0
  498. /package/dist/{treemap-qFGzn7xk.js → treemap-D-ka1hvx.js} +0 -0
  499. /package/dist/{troff-BZBk6AAu.js → troff-BHTsomIy.js} +0 -0
  500. /package/dist/{ttcn-DVwvXg0_.js → ttcn-DQuhn5Mn.js} +0 -0
  501. /package/dist/{ttcn-cfg-gjbVLf1L.js → ttcn-cfg-HjFYtdB-.js} +0 -0
  502. /package/dist/{turtle-CgxKXorV.js → turtle-nCay33Nv.js} +0 -0
  503. /package/dist/{vb-B9kSwTdM.js → vb-BG-XlqqJ.js} +0 -0
  504. /package/dist/{vbscript-DrUKSCdb.js → vbscript-B6vyW0-D.js} +0 -0
  505. /package/dist/{velocity-AlMYTnMy.js → velocity-CWegueqO.js} +0 -0
  506. /package/dist/{verilog-DLUaM05j.js → verilog-CzSQm4cG.js} +0 -0
  507. /package/dist/{vhdl-DUJOtSmO.js → vhdl-DqnNVL7r.js} +0 -0
  508. /package/dist/{webidl-CQp4aHk_.js → webidl-DXEUpDWH.js} +0 -0
  509. /package/dist/{xquery-IxkjlwOD.js → xquery-Ba_NB5bD.js} +0 -0
  510. /package/dist/{yacas-Bnctn5w8.js → yacas-HKQU6hyk.js} +0 -0
  511. /package/dist/{z80-DrFwhx53.js → z80-CXkHXLdj.js} +0 -0
@@ -0,0 +1,101 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { isEmpty } from "lodash-es";
4
+ import { InfoIcon } from "lucide-react";
5
+ import React from "react";
6
+ import { z } from "zod";
7
+
8
+ // A value worth rendering: drop null/undefined and empty containers
9
+ // (`{}`, `[]`), but keep meaningful primitives (`0`, `false`, `""`).
10
+ function isUninformative(value: unknown): boolean {
11
+ if (value == null) {
12
+ return true;
13
+ }
14
+ if (typeof value === "object") {
15
+ return isEmpty(value);
16
+ }
17
+ return false;
18
+ }
19
+
20
+ // Zod schema matching the Python SuccessResult dataclass
21
+ const SuccessResultSchema = z.looseObject({
22
+ status: z.string().default("success"),
23
+ auth_required: z.boolean().default(false),
24
+ action_url: z.any(),
25
+ next_steps: z.any(),
26
+ meta: z.any(),
27
+ message: z.string().nullish(),
28
+ });
29
+
30
+ type SuccessResult = z.infer<typeof SuccessResultSchema>;
31
+
32
+ const PrettySuccessResult: React.FC<{ data: SuccessResult }> = ({ data }) => {
33
+ const {
34
+ status,
35
+ auth_required,
36
+ action_url: _action_url,
37
+ meta: _meta,
38
+ next_steps: _next_steps,
39
+ message,
40
+ ...rest
41
+ } = data;
42
+
43
+ return (
44
+ <div className="flex flex-col gap-1.5">
45
+ <div className="flex items-center justify-between">
46
+ <h3 className="text-xs font-semibold text-muted-foreground">
47
+ Tool Result
48
+ </h3>
49
+ <div className="flex items-center gap-2">
50
+ <span className="text-xs px-2 py-0.5 bg-(--grass-2) text-(--grass-11) rounded-full font-medium capitalize">
51
+ {status}
52
+ </span>
53
+ {auth_required && (
54
+ <span className="text-xs px-2 py-0.5 bg-(--amber-2) text-(--amber-11) rounded-full">
55
+ Auth Required
56
+ </span>
57
+ )}
58
+ </div>
59
+ </div>
60
+
61
+ {message && (
62
+ <div className="flex items-start gap-2">
63
+ <InfoIcon className="h-3 w-3 text-(--blue-11) mt-0.5 shrink-0" />
64
+ <div className="text-xs text-foreground">{message}</div>
65
+ </div>
66
+ )}
67
+
68
+ {rest && (
69
+ <div className="space-y-3">
70
+ {Object.entries(rest).map(([key, value]) => {
71
+ if (isUninformative(value)) {
72
+ return null;
73
+ }
74
+ return (
75
+ <div key={key} className="space-y-1.5">
76
+ <span className="text-xs text-muted-foreground">{key}</span>
77
+ <pre className="bg-(--slate-2) p-2 text-muted-foreground border border-(--slate-4) rounded text-xs overflow-auto scrollbar-thin max-h-64">
78
+ {JSON.stringify(value, null, 2)}
79
+ </pre>
80
+ </div>
81
+ );
82
+ })}
83
+ </div>
84
+ )}
85
+ </div>
86
+ );
87
+ };
88
+
89
+ export const ResultRenderer: React.FC<{ result: unknown }> = ({ result }) => {
90
+ const parseResult = SuccessResultSchema.safeParse(result);
91
+
92
+ if (parseResult.success) {
93
+ return <PrettySuccessResult data={parseResult.data} />;
94
+ }
95
+
96
+ return (
97
+ <div className="text-xs font-medium text-muted-foreground mb-1 max-h-64 overflow-y-auto scrollbar-thin">
98
+ {typeof result === "string" ? result : JSON.stringify(result, null, 2)}
99
+ </div>
100
+ );
101
+ };
@@ -34,6 +34,7 @@ interface TableTopBarProps extends Partial<ExportActionProps> {
34
34
  showTableExplorer?: boolean;
35
35
  togglePanel?: (panelType: PanelType) => void;
36
36
  isAnyPanelOpen?: boolean;
37
+ sizeBytes?: number | null;
37
38
  }
38
39
 
39
40
  export const TableTopBar: React.FC<TableTopBarProps> = ({
@@ -48,6 +49,7 @@ export const TableTopBar: React.FC<TableTopBarProps> = ({
48
49
  togglePanel,
49
50
  isAnyPanelOpen,
50
51
  downloadAs,
52
+ sizeBytes,
51
53
  }) => {
52
54
  const [internalValue, setInternalValue] = useState(searchQuery || "");
53
55
  const debouncedSearch = useDebounce(internalValue, 500);
@@ -130,7 +132,9 @@ export const TableTopBar: React.FC<TableTopBarProps> = ({
130
132
  Explore
131
133
  </Button>
132
134
  )}
133
- {downloadAs && <ExportMenu downloadAs={downloadAs} />}
135
+ {downloadAs && (
136
+ <ExportMenu downloadAs={downloadAs} sizeBytes={sizeBytes} />
137
+ )}
134
138
  </div>
135
139
  </div>
136
140
  );
@@ -70,6 +70,9 @@ interface DataTableProps<TData> extends Partial<ExportActionProps> {
70
70
  setSorting?: OnChangeFn<SortingState>; // controlled sorting
71
71
  // Pagination
72
72
  totalRows: number | TooManyRows;
73
+ // JSON-serialized size of the currently-rendered data. Forwarded to
74
+ // ExportMenu so hosts can size-gate the Export button via downloadSizeLimitAtom.
75
+ sizeBytes?: number | null;
73
76
  totalColumns: number;
74
77
  pagination?: boolean;
75
78
  manualPagination?: boolean; // server-side pagination
@@ -121,6 +124,7 @@ const DataTableInternal = <TData,>({
121
124
  selection,
122
125
  totalColumns,
123
126
  totalRows,
127
+ sizeBytes,
124
128
  manualSorting = false,
125
129
  sorting,
126
130
  setSorting,
@@ -309,6 +313,7 @@ const DataTableInternal = <TData,>({
309
313
  togglePanel={togglePanel}
310
314
  isAnyPanelOpen={isAnyPanelOpen}
311
315
  downloadAs={downloadAs}
316
+ sizeBytes={sizeBytes}
312
317
  />
313
318
  <Table
314
319
  className={cn(
@@ -0,0 +1,10 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { atom } from "jotai";
4
+
5
+ export interface DownloadSizeLimit {
6
+ limitBytes: number;
7
+ unavailableMessage: string;
8
+ }
9
+
10
+ export const downloadSizeLimitAtom = atom<DownloadSizeLimit | null>(null);
@@ -1,5 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
+ import { useAtomValue } from "jotai";
3
4
  import {
4
5
  BracesIcon,
5
6
  BrickWallIcon,
@@ -9,6 +10,7 @@ import {
9
10
  } from "lucide-react";
10
11
  import React from "react";
11
12
  import { useLocale } from "react-aria";
13
+ import { downloadSizeLimitAtom } from "./download-policy/atoms";
12
14
  import { logNever } from "@/utils/assertNever";
13
15
  import { cn } from "@/utils/cn";
14
16
  import { copyToClipboard } from "@/utils/copy";
@@ -84,6 +86,11 @@ export interface ExportActionProps {
84
86
  error?: string | null;
85
87
  missing_packages?: string[] | null;
86
88
  }>;
89
+ // JSON-serialized size of the currently-rendered data. Used together with
90
+ // downloadSizeLimitAtom to disable the Export button when a host (e.g.,
91
+ // marimo-lsp inside VS Code) declares a download size cap. Null/undefined
92
+ // means "no info" and the gate stays disabled (fail-open).
93
+ sizeBytes?: number | null;
87
94
  }
88
95
 
89
96
  const labelForDownloadFormat = (format: DownloadFormat): string =>
@@ -94,12 +101,19 @@ const labelForCopyFormat = (format: CopyFormat): string =>
94
101
  export const ExportMenu: React.FC<ExportActionProps> = (props) => {
95
102
  const { locale } = useLocale();
96
103
  const [open, setOpen] = React.useState(false);
104
+ const policy = useAtomValue(downloadSizeLimitAtom);
105
+ const disabled = !!(
106
+ policy &&
107
+ props.sizeBytes != null &&
108
+ props.sizeBytes > policy.limitBytes
109
+ );
97
110
 
98
111
  const button = (
99
112
  <Button
100
113
  data-testid="export-button"
101
114
  size="xs"
102
115
  variant="text"
116
+ disabled={disabled}
103
117
  className={cn(
104
118
  "print:hidden text-xs gap-1",
105
119
  open ? "text-primary" : "text-muted-foreground",
@@ -113,7 +127,10 @@ export const ExportMenu: React.FC<ExportActionProps> = (props) => {
113
127
  const resolveDownloadUrl = async (
114
128
  format: DownloadFormat,
115
129
  onRetry: () => void,
116
- ): Promise<{ url: string; filename: string } | null> => {
130
+ ): Promise<{
131
+ url: string;
132
+ filename: string;
133
+ } | null> => {
117
134
  let response: Awaited<ReturnType<typeof props.downloadAs>>;
118
135
  try {
119
136
  response = await props.downloadAs({ format });
@@ -143,7 +160,10 @@ export const ExportMenu: React.FC<ExportActionProps> = (props) => {
143
160
  return null;
144
161
  }
145
162
 
146
- return { url: response.url, filename: response.filename };
163
+ return {
164
+ url: response.url,
165
+ filename: response.filename,
166
+ };
147
167
  };
148
168
 
149
169
  const handleDownload = async (format: DownloadFormat) => {
@@ -229,8 +249,15 @@ export const ExportMenu: React.FC<ExportActionProps> = (props) => {
229
249
 
230
250
  return (
231
251
  <DropdownMenu modal={false} open={open} onOpenChange={setOpen}>
232
- <Tooltip content="Export" open={open ? false : undefined}>
233
- <DropdownMenuTrigger asChild={true}>{button}</DropdownMenuTrigger>
252
+ <Tooltip
253
+ content={disabled ? policy?.unavailableMessage : "Export"}
254
+ open={open ? false : undefined}
255
+ >
256
+ <DropdownMenuTrigger asChild={true} disabled={disabled}>
257
+ <span tabIndex={disabled ? 0 : -1} className="inline-flex">
258
+ {button}
259
+ </span>
260
+ </DropdownMenuTrigger>
234
261
  </Tooltip>
235
262
  <DropdownMenuContent side="bottom" className="print:hidden">
236
263
  <DropdownMenuLabel className="text-xs text-muted-foreground">
@@ -53,6 +53,7 @@ import {
53
53
  canUndoDeletesAtom,
54
54
  getNotebook,
55
55
  hasDisabledCellsAtom,
56
+ undoLabelAtom,
56
57
  useCellActions,
57
58
  } from "@/core/cells/cells";
58
59
  import { disabledCellIds } from "@/core/cells/utils";
@@ -137,6 +138,7 @@ export function useNotebookActions() {
137
138
 
138
139
  const hasDisabledCells = useAtomValue(hasDisabledCellsAtom);
139
140
  const canUndoDeletes = useAtomValue(canUndoDeletesAtom);
141
+ const undoLabel = useAtomValue(undoLabelAtom);
140
142
  const { selectedLayout } = useLayoutState();
141
143
  const { setLayoutView } = useLayoutActions();
142
144
  const togglePresenting = useTogglePresenting();
@@ -525,7 +527,7 @@ export function useNotebookActions() {
525
527
  },
526
528
  {
527
529
  icon: <Undo2Icon size={14} strokeWidth={1.5} />,
528
- label: "Undo cell deletion",
530
+ label: undoLabel,
529
531
  hidden: !canUndoDeletes || kioskMode,
530
532
  handle: () => {
531
533
  undoDeleteCell();
@@ -15,6 +15,7 @@ interface Props {
15
15
  connection: ConnectionStatus;
16
16
  isRunning: boolean;
17
17
  width: AppConfig["width"];
18
+ onReconnect?: () => void;
18
19
  }
19
20
 
20
21
  export const AppContainer: React.FC<PropsWithChildren<Props>> = ({
@@ -22,13 +23,18 @@ export const AppContainer: React.FC<PropsWithChildren<Props>> = ({
22
23
  connection,
23
24
  isRunning,
24
25
  children,
26
+ onReconnect,
25
27
  }) => {
26
28
  const connectionState = connection.state;
27
29
 
28
30
  return (
29
31
  <>
30
32
  <DynamicFavicon isRunning={isRunning} />
31
- <StatusOverlay connection={connection} isRunning={isRunning} />
33
+ <StatusOverlay
34
+ connection={connection}
35
+ isRunning={isRunning}
36
+ onReconnect={onReconnect}
37
+ />
32
38
  <PyodideLoader>
33
39
  <WrappedWithSidebar>
34
40
  {/** oxlint-ignore-next-line -- ID is used by other components to grab the DOM element */}
@@ -27,6 +27,7 @@ import { Functions } from "@/utils/functions";
27
27
  import {
28
28
  canUndoDeletesAtom,
29
29
  needsRunAtom,
30
+ undoLabelAtom,
30
31
  useCellActions,
31
32
  } from "../../../core/cells/cells";
32
33
  import { ConfigButton } from "../../app-config/app-config-button";
@@ -56,6 +57,7 @@ export const Controls = ({
56
57
  running,
57
58
  }: ControlsProps): JSX.Element => {
58
59
  const undoAvailable = useAtomValue(canUndoDeletesAtom);
60
+ const undoLabel = useAtomValue(undoLabelAtom);
59
61
  const needsRun = useAtomValue(needsRunAtom);
60
62
  const { undoDeleteCell } = useCellActions();
61
63
  const closed = connectionState === WebSocketState.CLOSED;
@@ -63,7 +65,7 @@ export const Controls = ({
63
65
  let undoControl: JSX.Element | null = null;
64
66
  if (!closed && undoAvailable) {
65
67
  undoControl = (
66
- <Tooltip content="Undo cell deletion">
68
+ <Tooltip content={undoLabel}>
67
69
  <Button
68
70
  data-testid="undo-delete-cell"
69
71
  size="medium"
@@ -11,6 +11,9 @@ import { prettyError } from "@/utils/errors";
11
11
  import { Functions } from "@/utils/functions";
12
12
  import { type FilePath, PathBuilder } from "@/utils/paths";
13
13
  import { resolvePaths } from "@/utils/pathUtils";
14
+ import { mapWithConcurrency } from "@/utils/semaphore";
15
+
16
+ const FILE_OP_CONCURRENCY = 5;
14
17
 
15
18
  /**
16
19
  * Normalized result of a file mutation: the server response when successful,
@@ -159,28 +162,26 @@ export class RequestingTree {
159
162
  ? (this.delegate.find(parentId)?.data.path ?? parentId)
160
163
  : this.rootPath;
161
164
 
162
- await Promise.all(
163
- fromIds.map(async (id) => {
164
- const node = this.delegate.find(id);
165
- if (!node) {
166
- return;
167
- }
168
- const originalPath = node.data.path;
169
- const newPath = this.path.join(
170
- parentPath,
171
- this.path.basename(originalPath as FilePath),
172
- );
173
- const result = await this.callbacks
174
- .renameFileOrFolder({ path: originalPath, newPath })
175
- .then(handleFileResponse);
176
- if (!result) {
177
- return;
178
- }
165
+ await mapWithConcurrency(fromIds, FILE_OP_CONCURRENCY, async (id) => {
166
+ const node = this.delegate.find(id);
167
+ if (!node) {
168
+ return;
169
+ }
170
+ const originalPath = node.data.path;
171
+ const newPath = this.path.join(
172
+ parentPath,
173
+ this.path.basename(originalPath as FilePath),
174
+ );
175
+ const result = await this.callbacks
176
+ .renameFileOrFolder({ path: originalPath, newPath })
177
+ .then(handleFileResponse);
178
+ if (!result) {
179
+ return;
180
+ }
179
181
 
180
- this.delegate.move({ id, parentId, index: 0 });
181
- this.delegate.update({ id, changes: { path: newPath } });
182
- }),
183
- );
182
+ this.delegate.move({ id, parentId, index: 0 });
183
+ this.delegate.update({ id, changes: { path: newPath } });
184
+ });
184
185
 
185
186
  this.onChange(this.delegate.data);
186
187
 
@@ -262,11 +263,12 @@ export class RequestingTree {
262
263
  this.rootPath,
263
264
  ...ids.map((id) => this.delegate.find(id)?.data.path),
264
265
  ].filter(Boolean);
265
- // Request all folders in parallel, and catch any errors
266
- const data = await Promise.all(
267
- openFolders.map((path) =>
266
+ // Request open folders with bounded concurrency; swallow per-folder errors.
267
+ const data = await mapWithConcurrency(
268
+ openFolders,
269
+ FILE_OP_CONCURRENCY,
270
+ (path) =>
268
271
  this.callbacks.listFiles({ path: path }).catch(() => ({ files: [] })),
269
- ),
270
272
  );
271
273
 
272
274
  for (const [idx, openFolder] of openFolders.entries()) {
@@ -3,13 +3,14 @@
3
3
  import { type DropzoneOptions, useDropzone } from "react-dropzone";
4
4
  import { toast } from "@/components/ui/use-toast";
5
5
  import { useRequestClient } from "@/core/network/requests";
6
- import { serializeBlob } from "@/utils/blob";
7
6
  import { withLoadingToast } from "@/utils/download";
8
7
  import { Logger } from "@/utils/Logger";
9
8
  import { type FilePath, PathBuilder } from "@/utils/paths";
9
+ import { mapWithConcurrency } from "@/utils/semaphore";
10
10
  import { refreshRoot } from "./state";
11
11
 
12
12
  const MAX_SIZE = 1024 * 1024 * 100; // 100MB
13
+ const UPLOAD_CONCURRENCY = 5;
13
14
 
14
15
  export function useFileExplorerUpload(options: DropzoneOptions = {}) {
15
16
  const { sendCreateFileOrFolder } = useRequestClient();
@@ -59,30 +60,28 @@ export function useFileExplorerUpload(options: DropzoneOptions = {}) {
59
60
  loadingTitle,
60
61
  async (progress) => {
61
62
  progress.addTotal(acceptedFiles.length);
62
- for (const file of acceptedFiles) {
63
- // We strip the leading slash since File.path can return
64
- // `/path/to/file`.
65
- const filePath = stripLeadingSlash(getPath(file));
66
- let directoryPath = "" as FilePath;
67
- if (filePath) {
68
- directoryPath =
69
- PathBuilder.guessDeliminator(filePath).dirname(filePath);
70
- }
63
+ await mapWithConcurrency(
64
+ acceptedFiles,
65
+ UPLOAD_CONCURRENCY,
66
+ async (file) => {
67
+ // We strip the leading slash since File.path can return
68
+ // `/path/to/file`.
69
+ const filePath = stripLeadingSlash(getPath(file));
70
+ let directoryPath = "" as FilePath;
71
+ if (filePath) {
72
+ directoryPath =
73
+ PathBuilder.guessDeliminator(filePath).dirname(filePath);
74
+ }
71
75
 
72
- // File contents are sent base64-encoded to support arbitrary
73
- // bytes data
74
- //
75
- // get the raw base64-encoded data from a string starting with
76
- // data:*/*;base64,
77
- const base64 = (await serializeBlob(file)).split(",")[1];
78
- await sendCreateFileOrFolder({
79
- path: directoryPath,
80
- type: "file",
81
- name: file.name,
82
- contents: base64,
83
- });
84
- progress.increment(1);
85
- }
76
+ await sendCreateFileOrFolder({
77
+ path: directoryPath,
78
+ type: "file",
79
+ name: file.name,
80
+ file,
81
+ });
82
+ progress.increment(1);
83
+ },
84
+ );
86
85
  await refreshRoot();
87
86
  },
88
87
  onFinish,
@@ -0,0 +1,108 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ // @vitest-environment jsdom
3
+
4
+ import { fireEvent, render } from "@testing-library/react";
5
+ import { createStore, Provider as JotaiProvider } from "jotai";
6
+ import type React from "react";
7
+ import { describe, expect, it, vi } from "vitest";
8
+ import { TooltipProvider } from "@/components/ui/tooltip";
9
+ import { viewStateAtom } from "@/core/mode";
10
+ import {
11
+ type ConnectionStatus,
12
+ WebSocketClosedReason,
13
+ WebSocketState,
14
+ } from "@/core/websocket/types";
15
+ import { StatusOverlay } from "../status";
16
+
17
+ function renderOverlay(
18
+ connection: ConnectionStatus,
19
+ onReconnect?: () => void,
20
+ ): ReturnType<typeof render> {
21
+ const store = createStore();
22
+ store.set(viewStateAtom, { mode: "edit", cellAnchor: null });
23
+ const wrapper: React.FC<React.PropsWithChildren> = ({ children }) => (
24
+ <JotaiProvider store={store}>
25
+ <TooltipProvider>{children}</TooltipProvider>
26
+ </JotaiProvider>
27
+ );
28
+ return render(
29
+ <StatusOverlay
30
+ connection={connection}
31
+ isRunning={false}
32
+ onReconnect={onReconnect}
33
+ />,
34
+ { wrapper },
35
+ );
36
+ }
37
+
38
+ describe("StatusOverlay disconnect indicator", () => {
39
+ it("invokes onReconnect when the disconnect icon is clicked", () => {
40
+ const onReconnect = vi.fn();
41
+ const { getByTestId } = renderOverlay(
42
+ {
43
+ state: WebSocketState.CLOSED,
44
+ code: WebSocketClosedReason.KERNEL_DISCONNECTED,
45
+ reason: "kernel not found",
46
+ },
47
+ onReconnect,
48
+ );
49
+
50
+ const icon = getByTestId("disconnected-indicator") as HTMLButtonElement;
51
+ expect(icon.tagName).toBe("BUTTON");
52
+ expect(icon.disabled).toBe(false);
53
+ expect(icon.getAttribute("aria-label")).toBe("Reconnect to app");
54
+ fireEvent.click(icon);
55
+ expect(onReconnect).toHaveBeenCalledTimes(1);
56
+ });
57
+
58
+ it("renders a disabled button when no onReconnect is provided", () => {
59
+ const { getByTestId } = renderOverlay({
60
+ state: WebSocketState.CLOSED,
61
+ code: WebSocketClosedReason.KERNEL_DISCONNECTED,
62
+ reason: "kernel not found",
63
+ });
64
+
65
+ const button = getByTestId("disconnected-indicator");
66
+ expect((button as HTMLButtonElement).disabled).toBe(true);
67
+ });
68
+
69
+ it.each([
70
+ [
71
+ WebSocketClosedReason.MALFORMED_QUERY,
72
+ "the kernel did not recognize a request; please file a bug with marimo",
73
+ ],
74
+ [
75
+ WebSocketClosedReason.KERNEL_STARTUP_ERROR,
76
+ "Failed to start kernel sandbox",
77
+ ],
78
+ ])(
79
+ "renders a disabled button for non-recoverable close reason %s",
80
+ (code, reason) => {
81
+ const onReconnect = vi.fn();
82
+ const { getByTestId } = renderOverlay(
83
+ { state: WebSocketState.CLOSED, code, reason },
84
+ onReconnect,
85
+ );
86
+
87
+ const button = getByTestId("disconnected-indicator") as HTMLButtonElement;
88
+ expect(button.disabled).toBe(true);
89
+ fireEvent.click(button);
90
+ expect(onReconnect).not.toHaveBeenCalled();
91
+ },
92
+ );
93
+
94
+ it("does not render the disconnect icon when another tab has taken over", () => {
95
+ const onReconnect = vi.fn();
96
+ const { queryByTestId } = renderOverlay(
97
+ {
98
+ state: WebSocketState.CLOSED,
99
+ code: WebSocketClosedReason.ALREADY_RUNNING,
100
+ reason: "another browser tab is already connected to the kernel",
101
+ canTakeover: true,
102
+ },
103
+ onReconnect,
104
+ );
105
+
106
+ expect(queryByTestId("disconnected-indicator")).toBeNull();
107
+ });
108
+ });
@@ -7,16 +7,26 @@ import { Tooltip } from "@/components/ui/tooltip";
7
7
  import { notebookScrollToRunning } from "@/core/cells/actions";
8
8
  import { onlyScratchpadIsRunningAtom } from "@/core/cells/cells";
9
9
  import { viewStateAtom } from "@/core/mode";
10
- import { type ConnectionStatus, WebSocketState } from "@/core/websocket/types";
10
+ import {
11
+ type ConnectionStatus,
12
+ WebSocketClosedReason,
13
+ WebSocketState,
14
+ } from "@/core/websocket/types";
11
15
  import { cn } from "@/utils/cn";
12
16
 
13
17
  export const StatusOverlay: React.FC<{
14
18
  connection: ConnectionStatus;
15
19
  isRunning: boolean;
16
- }> = ({ connection, isRunning }) => {
20
+ onReconnect?: () => void;
21
+ }> = ({ connection, isRunning, onReconnect }) => {
17
22
  const { mode } = useAtomValue(viewStateAtom);
18
23
  const isClosed = connection.state === WebSocketState.CLOSED;
19
24
  const isOpen = connection.state === WebSocketState.OPEN;
25
+ // Only KERNEL_DISCONNECTED is recoverable by a retry. Other terminal
26
+ // reasons (MALFORMED_QUERY, KERNEL_STARTUP_ERROR) would deterministically
27
+ // fail the same way; ALREADY_RUNNING is handled by `LockedIcon` below.
28
+ const canReconnect =
29
+ isClosed && connection.code === WebSocketClosedReason.KERNEL_DISCONNECTED;
20
30
 
21
31
  return (
22
32
  <>
@@ -28,7 +38,11 @@ export const StatusOverlay: React.FC<{
28
38
  )}
29
39
  >
30
40
  {isOpen && isRunning && <RunningIcon />}
31
- {isClosed && !connection.canTakeover && <DisconnectedIcon />}
41
+ {isClosed && !connection.canTakeover && (
42
+ <DisconnectedIcon
43
+ onReconnect={canReconnect ? onReconnect : undefined}
44
+ />
45
+ )}
32
46
  {isClosed && connection.canTakeover && <LockedIcon />}
33
47
  </div>
34
48
  </>
@@ -37,13 +51,33 @@ export const StatusOverlay: React.FC<{
37
51
 
38
52
  const topLeftStatus = "print:hidden pointer-events-auto hover:cursor-pointer";
39
53
 
40
- const DisconnectedIcon = () => (
41
- <Tooltip content="App disconnected">
42
- <div className={topLeftStatus}>
43
- <UnlinkIcon className="w-[25px] h-[25px] text-(--red-11)" />
44
- </div>
45
- </Tooltip>
46
- );
54
+ const DisconnectedIcon: React.FC<{ onReconnect?: () => void }> = ({
55
+ onReconnect,
56
+ }) => {
57
+ const disabled = !onReconnect;
58
+ return (
59
+ <Tooltip
60
+ content={
61
+ disabled ? "App disconnected" : "App disconnected — click to reconnect"
62
+ }
63
+ >
64
+ {/* Wrapper span keeps the tooltip reachable when the button is
65
+ disabled — a disabled <button> swallows pointer events. */}
66
+ <span tabIndex={disabled ? 0 : -1}>
67
+ <button
68
+ type="button"
69
+ className={cn(topLeftStatus, "bg-transparent border-0 p-0")}
70
+ aria-label={disabled ? "App disconnected" : "Reconnect to app"}
71
+ data-testid="disconnected-indicator"
72
+ onClick={onReconnect}
73
+ disabled={disabled}
74
+ >
75
+ <UnlinkIcon className="w-[25px] h-[25px] text-(--red-11)" />
76
+ </button>
77
+ </span>
78
+ </Tooltip>
79
+ );
80
+ };
47
81
 
48
82
  const LockedIcon = () => (
49
83
  <Tooltip content="Notebook locked">