@marimo-team/islands 0.24.3-dev9 → 0.25.1-dev1

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 (440) hide show
  1. package/dist/{ConnectedDataExplorerComponent-C9OF_75w.js → ConnectedDataExplorerComponent-KVhjDMSe.js} +1066 -1066
  2. package/dist/ErrorBoundary-pXXIvlr3.js +200 -0
  3. package/dist/{ImageComparisonComponent-2a9TMMm2.js → ImageComparisonComponent-Dlm5aWnH.js} +4 -4
  4. package/dist/{Plot-C-9MQeGP.js → Plot-CTyjbQDT.js} +21032 -20666
  5. package/dist/_baseMerge-BiGRwuqv.js +165 -0
  6. package/dist/{abnfDiagram-N423BO3Z-CQKwzX8t.js → abnfDiagram-VCTEODGH-CpN0FRda.js} +17 -18
  7. package/dist/{any-language-editor-DmQ7dyCx.js → any-language-editor-CycIVIeP.js} +17 -17
  8. package/dist/arc-7ra3iNdN.js +81 -0
  9. package/dist/architecture-7GRP2DOG-BWLylgiA.js +3 -0
  10. package/dist/{architectureDiagram-T3A2C74G-CncrDpfa.js → architectureDiagram-5GKGNRK7-DDyj5SRB.js} +25 -25
  11. package/dist/assets/__vite-browser-external-C9iR87Zl.js +1 -0
  12. package/dist/assets/worker-D7hWi02M.js +109 -0
  13. package/dist/blockDiagram-I7D4REHJ-BbXEXJRu.js +1922 -0
  14. package/dist/{button-CKBkqR9J.js → button-DdXAm8iz.js} +5 -5
  15. package/dist/{c4Diagram-5PPSVZJV-DarUk3Yq.js → c4Diagram-7LVT6UL2-DcLsbQmR.js} +696 -668
  16. package/dist/{capabilities-CMBBJTcc.js → capabilities-B3ce_GNu.js} +1 -1
  17. package/dist/{html-to-image-BtFMe_Nq.js → cells-DYIkSxs6.js} +6757 -10137
  18. package/dist/{channel-CyiYUzJv.js → channel-B5XnTarr.js} +1 -1
  19. package/dist/chat-ui-BXj3uYjy.js +5786 -0
  20. package/dist/{check-CHig47QV.js → check-B7cIK6gb.js} +1 -1
  21. package/dist/{chunk-2Q5K7J3B-BMuJl2jB.js → chunk-2Q5K7J3B-BMkUkeuy.js} +2 -2
  22. package/dist/chunk-4HAMMTFA-D2qv8E0X.js +4188 -0
  23. package/dist/{chunk-5VM5RSS4-Bcq0ftdy.js → chunk-5VM5RSS4-DKsmPCL6.js} +2 -2
  24. package/dist/{chunk-NSK5VX7P-CDTMcJB0.js → chunk-75Z2AOVW-5_HsvXwr.js} +23 -23
  25. package/dist/{chunk-I66GZJ75-BqyTCAPf.js → chunk-DU6HZSFF-BUgAHvnM.js} +21 -4
  26. package/dist/{chunk-2GRJ4B5K-DgO8Qudq.js → chunk-F27PBJKO-DHmYWZXr.js} +4 -4
  27. package/dist/{chunk-KEIR6QF5-C4KX3XYh.js → chunk-FOHPRMQF-BpYR8RqP.js} +4 -4
  28. package/dist/{chunk-WRU74C26-BpWsbiDK.js → chunk-GMAD6QVW-Czynz8aA.js} +486 -258
  29. package/dist/chunk-GVQU2GXP-yQEdulRS.js +37 -0
  30. package/dist/{chunk-5RXB4S5H-tLNUVDdO.js → chunk-IMKFNOWR-BfwTebD0.js} +11 -11
  31. package/dist/{chunk-JWPE2WC7-BJwjGUnF.js → chunk-JWPE2WC7-DJvv1t8U.js} +2 -2
  32. package/dist/chunk-L3NEJ4N5-DwRWnDDK.js +245 -0
  33. package/dist/{chunk-7Z6QIM7H-Dc18hFxH.js → chunk-OSK3NFVY-BBKU_Ign.js} +222 -144
  34. package/dist/{chunk-W5SLKNZC-DLnvpEtV.js → chunk-P2QGCYS3-C0iGU29N.js} +20 -20
  35. package/dist/{chunk-KBJHAD2P-BuqWqkcW.js → chunk-POPQ4Y6H-BCbS3Ozc.js} +4 -4
  36. package/dist/{chunk-4I5QYGJK-DpytHuZQ.js → chunk-PWAF6VOD-CTFCL0jI.js} +4 -4
  37. package/dist/{chunk-JQJVKLGR-CgqCwe8E.js → chunk-SHT3W25Y-BspJEJ5H.js} +109 -63
  38. package/dist/{chunk-6Q2QTUOP-DmTcEBMn.js → chunk-SVP7TREG-DX3rFNTe.js} +5 -5
  39. package/dist/{chunk-GF5L2VYU-B0wgCQgS.js → chunk-TICWLB2K-fVF7Uujg.js} +13 -13
  40. package/dist/{chunk-XXDRQBXY-D73H4IN0.js → chunk-XXDRQBXY-Dnl5VBqG.js} +3 -3
  41. package/dist/{chunk-Y2CYZVJY-DoegUj_e.js → chunk-Y2CYZVJY-DKKS1VyV.js} +1 -1
  42. package/dist/{chunk-5FQGJX7Z-B85XCPv6.js → chunk-YOKDWASO-DrWbKnxA.js} +6925 -5460
  43. package/dist/classDiagram-ZZMXUADV-Fe-V14Sc.js +30 -0
  44. package/dist/classDiagram-v2-VYDZK3BY-xTDg7lOs.js +30 -0
  45. package/dist/code-visibility-BC_yF09p.js +138 -0
  46. package/dist/{common-Cv-4hMa0.js → common-Dv227ygK.js} +8838 -12645
  47. package/dist/{compiler-runtime-CAH9RLw2.js → compiler-runtime-Cj8ZWiNS.js} +2 -2
  48. package/dist/copy-icon-CLdENY__.js +190 -0
  49. package/dist/{cose-bilkent-JH36ORCC-FUWHkXFX.js → cose-bilkent-JH36ORCC-_LyRKVqm.js} +2 -2
  50. package/dist/cubic-OaEBhQPK.js +1404 -0
  51. package/dist/cynefin-OW5HDTMX-obq-i80l.js +3 -0
  52. package/dist/{cynefinDiagram-MW4NZA55-BXSFK93J.js → cynefinDiagram-5FMLGOSQ-CFQrqzSX.js} +10 -10
  53. package/dist/{dagre-CGyalH38.js → dagre-BGSfXNEb.js} +678 -537
  54. package/dist/dagre-GXQ25YYZ-xHNAUTyG.js +425 -0
  55. package/dist/{data-grid-overlay-editor-DZM4BAaI.js → data-grid-overlay-editor-B-I2K7z3.js} +8 -9
  56. package/dist/{data-grid-types-BnZwWG8z.js → data-grid-types-BucVmDDp.js} +1 -1
  57. package/dist/{defaultLocale-DKaU_opG.js → defaultLocale-JI_lnoCe.js} +1 -1
  58. package/dist/{diagram-7IWD3JNH-BjfzDNvj.js → diagram-S7CK7UJ4-Bt1rPbAx.js} +40 -39
  59. package/dist/{diagram-UB23O5K3-DDg-7wLA.js → diagram-UQ7AKVKN-D1sq5FoZ.js} +10 -10
  60. package/dist/{diagram-B4RE2ZJO-CKccR9eE.js → diagram-VSXAHHWV-BKpmG23x.js} +10 -10
  61. package/dist/{diagram-Q27KOJAE-Zhz01ss7.js → diagram-VX7I27RA-BMwGFVD6.js} +21 -21
  62. package/dist/{diagram-LBJQPF4R-CJ-bOruh.js → diagram-Z3DM3KII--KLDR9Pl.js} +10 -10
  63. package/dist/dialog-CgNq6y9-.js +94 -0
  64. package/dist/{dist-dAqZAPcF.js → dist-1HDa70oE.js} +3 -3
  65. package/dist/{dist-Re9sOPOx.js → dist-9x1ZRe-e.js} +2 -2
  66. package/dist/{dist-DEeLqRlU.js → dist-B1vYLK28.js} +1 -1
  67. package/dist/{dist-C5Bn52W4.js → dist-B8D7McG3.js} +3 -3
  68. package/dist/{dist-_XvgytiS.js → dist-BHCHt1NI.js} +1 -1
  69. package/dist/{dist-CRjAdnqF.js → dist-BRxfaJgs.js} +3 -3
  70. package/dist/{dist-Byj2bV8o.js → dist-BVGtK7Ht.js} +1 -1
  71. package/dist/{dist-BIN5LKyE.js → dist-BaHjhkpZ.js} +2 -2
  72. package/dist/{dist-DVFbIwc5.js → dist-BtKIs9N-.js} +1 -1
  73. package/dist/{dist-BrQGcfQ9.js → dist-C2KzV-vf.js} +2 -2
  74. package/dist/{dist-DJFiGP2k.js → dist-C2wDlYdD.js} +2 -2
  75. package/dist/{dist-BVRM2y1P.js → dist-C8xMLVYi.js} +2 -2
  76. package/dist/dist-CMh_W4y4.js +538 -0
  77. package/dist/{extends-BPB6_lTK.js → dist-CO1AwwG_.js} +516 -561
  78. package/dist/{dist-BzN60Z2s.js → dist-CPGxfVGX.js} +1 -1
  79. package/dist/{dist-C7v-9nUK.js → dist-Cm3iAFMD.js} +3 -3
  80. package/dist/{dist-CGAxEiaM.js → dist-Cs2FijYk.js} +2 -2
  81. package/dist/{dist-hXd65f0_.js → dist-D1PpnuZB.js} +1 -1
  82. package/dist/{dist-VdvbxG72.js → dist-DHOTQar0.js} +1 -1
  83. package/dist/{dist-DcgzXCfy.js → dist-DPcEtqmG.js} +2 -2
  84. package/dist/{dist-D4Z_qdP4.js → dist-Df0wKI0F.js} +1 -1
  85. package/dist/{dist-qhLI-SHd.js → dist-DszxuxS1.js} +2 -2
  86. package/dist/{dist-BEmkrGoQ.js → dist-Kxgjxakw.js} +1 -1
  87. package/dist/{dist-BbSMhhD2.js → dist-KzZ7WELx.js} +2 -2
  88. package/dist/{dist-jWUab2d1.js → dist-lA1Cttkb.js} +1 -1
  89. package/dist/dropdown-menu-CZgjYSAh.js +4502 -0
  90. package/dist/{ebnfDiagram-BXEA7PRR-Dm75pCAS.js → ebnfDiagram-PWID7BFC-WnG4cvSQ.js} +17 -18
  91. package/dist/{erDiagram-JOGREHBK-rVDG3VSQ.js → erDiagram-RLTQ6QDP-DhzKiYHc.js} +1037 -798
  92. package/dist/error-banner-B94dcDmP.js +391 -0
  93. package/dist/error-entries-C0NsUqXl.js +56 -0
  94. package/dist/{esm-CR1nhb1c.js → esm-BElI3qKH.js} +22 -22
  95. package/dist/{esm-C1d0g6Bv.js → esm-G4Fhsgng.js} +51 -37
  96. package/dist/{esm-CesmlDrs.js → esm-h8f7-1CJ.js} +423 -423
  97. package/dist/eventmodeling-NTZA5JFV-D7nP7Uyu.js +3 -0
  98. package/dist/feedback-button-D2hUXe09.js +380 -0
  99. package/dist/flowDiagram-HODETNUW-6dMdB8pR.js +16 -0
  100. package/dist/{formats-CslNOKy0.js → formats-BR5iGb4s.js} +8 -8
  101. package/dist/{ganttDiagram-PKOTCBZU-9D-LXF6P.js → ganttDiagram-EL5Y4UJY-DbzEBLbl.js} +15 -15
  102. package/dist/gitGraph-4MIJSDKK-Dy9uQXGh.js +3 -0
  103. package/dist/{gitGraphDiagram-DS77QQ5N-BfpsXTLf.js → gitGraphDiagram-WWUBYQGX-BMf0iKi1.js} +10 -10
  104. package/dist/{glide-data-editor-sPZ8fUFn.js → glide-data-editor-B851uguN.js} +2143 -1910
  105. package/dist/{glide-utils-NShAPPtn.js → glide-utils-B3rUT5PI.js} +15 -15
  106. package/dist/globals-CmijFoIU.js +19 -0
  107. package/dist/highlighted-body-KPVGNVTW-Ad3MKZ18.js +38 -0
  108. package/dist/html-to-image-CeYYvQNb.js +3272 -0
  109. package/dist/info-A6RAGUB7-DKoayiHY.js +3 -0
  110. package/dist/{infoDiagram-6WML65LV-DqCTXHVy.js → infoDiagram-27XIBGKW-BagtCX_5.js} +7 -7
  111. package/dist/{ishikawaDiagram-WSZJBQD7-BCUYmZ1v.js → ishikawaDiagram-5VMMS53U-DByDehYy.js} +8 -8
  112. package/dist/{journeyDiagram-NVQOT4AX-BXB6M4zN.js → journeyDiagram-3NMN7TZE-D2LcKDUO.js} +8 -8
  113. package/dist/{jsx-runtime-CpoKNRgu.js → jsx-runtime-DgDPJV9S.js} +1 -1
  114. package/dist/{kanban-definition-27J2QSJJ-CGRhrDUX.js → kanban-definition-UXKFOSKX-BxwkbBI_.js} +29 -29
  115. package/dist/katex-DFBiSucW.js +21840 -0
  116. package/dist/{label-lwzzBHUW.js → label-BJ49YTvk.js} +6 -6
  117. package/dist/{linear-D5r_1YqD.js → linear-Cnaq3Tfb.js} +4 -4
  118. package/dist/{loader-VZ__bv4g.js → loader-D2W4oM-3.js} +15 -15
  119. package/dist/main.js +8113 -13475
  120. package/dist/mermaid-DPNuhemn.js +3084 -0
  121. package/dist/mermaid-HWGCJPDP-DYCzSILp.js +2 -0
  122. package/dist/mermaid-parser.core-DxuSfYwJ.js +638 -0
  123. package/dist/{mhchem-BjwrxcV-.js → mhchem-DWCF06w9.js} +1 -1
  124. package/dist/{micromark-factory-space-DruVGzeG.js → micromark-factory-space-DGxAzmw8.js} +2 -2
  125. package/dist/{mindmap-definition-FAOFIHXS-C9gRhzZq.js → mindmap-definition-YA3MSWOX-Cgvojw3y.js} +17 -17
  126. package/dist/{node-sql-parser-DesT1xNw.js → node-sql-parser-BiZOJ-FS.js} +0 -3
  127. package/dist/{number-overlay-editor-De2CabLL.js → number-overlay-editor-BtdoxWiD.js} +5 -5
  128. package/dist/objectWithoutPropertiesLoose-BRDcTsfB.js +23 -0
  129. package/dist/packet-AYTQ26CC-Yw7ajchJ.js +3 -0
  130. package/dist/{pegDiagram-VL7TDLO6-Df9IUjMD.js → pegDiagram-XKGWAZYB-DahHhYyN.js} +19 -20
  131. package/dist/{pick-SnHlmIBN.js → pick-CJeoxYOS.js} +1 -1
  132. package/dist/pie-WAS4IAKB-BKdKOAkL.js +3 -0
  133. package/dist/{pieDiagram-7S7Q4E2Y-BmRzQ8ZP.js → pieDiagram-E7YTZNPT-CCtX0oCK.js} +29 -31
  134. package/dist/{process-output-CPy0YKUe.js → process-output-BCTjlyuH.js} +7 -6
  135. package/dist/purify.es-DgKyQviY.js +805 -0
  136. package/dist/{quadrantDiagram-CIZ2JOQS-wFBvWcxS.js → quadrantDiagram-AXDQQJYC-DftINo6g.js} +5 -5
  137. package/dist/radar-RG4KPBEZ-ScR_D_Ts.js +3 -0
  138. package/dist/railroad-74A4TZTK-DQvPSdo5.js +3 -0
  139. package/dist/railroad-abnf-HS5TGJTU-CsJQG2O8.js +3 -0
  140. package/dist/railroad-ebnf-LZEXJU2U-BgEobkSn.js +3 -0
  141. package/dist/railroad-peg-WCYAUIDC-DjLAjofI.js +3 -0
  142. package/dist/{railroadDiagram-AXF67PYL-DxbIatiN.js → railroadDiagram-O6MQD6OU-Du5NsPUb.js} +17 -18
  143. package/dist/{react-dom-C94wmq5w.js → react-dom-BUgJ-ab_.js} +3 -3
  144. package/dist/{react-DMWcHin-.js → react-ofJbxM4x.js} +2 -2
  145. package/dist/{react-vega-DSD5aT7_.js → react-vega-5AcMW8gG.js} +10012 -9369
  146. package/dist/{requirementDiagram-LRYGKXZP-X_x-Y713.js → requirementDiagram-BXWQKSXE-0pmmxL6_.js} +20 -19
  147. package/dist/{reveal-component-CVsYlb9J.js → reveal-component-DQq7NUGJ.js} +1303 -1237
  148. package/dist/{sankeyDiagram-W5VNT64P-BYJALbbm.js → sankeyDiagram-P5KCCOFB-CDKR5TJH.js} +4 -4
  149. package/dist/{select-3qHpFKF9.js → select-C6plhmZr.js} +317 -562
  150. package/dist/{sequenceDiagram-SI44F4Z6-BiPBe3Sv.js → sequenceDiagram-WJ2MYXX4-DB-ChWwU.js} +11 -11
  151. package/dist/{sizeCapture-X5ZJPWSS-BKLDeh5H.js → sizeCapture-INFHLROL-Dx5SpkQF.js} +2 -2
  152. package/dist/{spec-CutWdqo3.js → spec-CAYwFeV6.js} +4 -4
  153. package/dist/{src-C31ngt98.js → src-CA_m4ONl.js} +298 -303
  154. package/dist/{stateDiagram-OKZ733FA-B7QyNBxH.js → stateDiagram-D77RDMKH-orN6AwMx.js} +19 -20
  155. package/dist/stateDiagram-v2-MP3YSRHH-o05_rJN8.js +30 -0
  156. package/dist/style.css +2 -2
  157. package/dist/{swimlanes-SLNWSIFB-B4f8VP3b.js → swimlanes-42K2YHIH-BkSDcqDT.js} +1501 -1591
  158. package/dist/swimlanesDiagram-VR7AAH4N-D_nFQfRc.js +30 -0
  159. package/dist/{swiper-component-C90YjhaV.js → swiper-component-IOUowglM.js} +39 -26
  160. package/dist/{time-aWGfjN8x.js → time-BieKwdYS.js} +42 -42
  161. package/dist/{timeline-definition-Z64GVDOM-Dacq_5lr.js → timeline-definition-24CTP7MA-BuqJUIRZ.js} +7 -7
  162. package/dist/{toDate-CpXBeDq0.js → toDate-WXg1yLcI.js} +7 -7
  163. package/dist/toast-DvY70eFX.js +1834 -0
  164. package/dist/tooltip-qB8NlSqV.js +1742 -0
  165. package/dist/treeView-Q6P3EWNA-DuRV97YF.js +3 -0
  166. package/dist/treemap-WGGIJYW6-AsLq-uwC.js +3 -0
  167. package/dist/use-restore-focus-D5D9xEjZ.js +5946 -0
  168. package/dist/{use-vega-container-remeasure-DUW4tRxq.js → use-vega-container-remeasure-4DwJbPWu.js} +2 -2
  169. package/dist/{useAsyncData-CH6-CgJF.js → useAsyncData-BD6AStMt.js} +3 -3
  170. package/dist/{useDateFormatter-Cp9Jdxls.js → useDateFormatter-BRTPsUK7.js} +8 -8
  171. package/dist/{useDeepCompareMemoize-JC87C_jO.js → useDeepCompareMemoize-D_HNdyvI.js} +2 -2
  172. package/dist/{useIframeCapabilities-De3C-9b4.js → useIframeCapabilities-CVtNkI3Y.js} +2 -2
  173. package/dist/{useLifecycle-B4RGlyf0.js → useLifecycle-DPgiiwwU.js} +6 -6
  174. package/dist/useNumberFormatter-BfDpa21j.js +251 -0
  175. package/dist/useTheme-DzXmYZ5V.js +904 -0
  176. package/dist/{vega-component-ovBXgp91.js → vega-component-D0QXopU8.js} +34 -34
  177. package/dist/{vega-embed-container-TJ5z5dZw.js → vega-embed-container-CbnTccZi.js} +6 -6
  178. package/dist/vega-loader.browser-D95WfUcj.js +1139 -0
  179. package/dist/{vennDiagram-T6HMQDX7-D-Rcq3Iu.js → vennDiagram-4TSXK5OY-CTUlJ2PK.js} +9 -9
  180. package/dist/wardley-WFR3VGLG-Na-tFhQJ.js +3 -0
  181. package/dist/{wardleyDiagram-T6FBY63Y-Z-Og7_Wd.js → wardleyDiagram-VM6X3IG4-3evLUb0M.js} +10 -10
  182. package/dist/{xychartDiagram-ELKLHX3M-BDJ2dITA.js → xychartDiagram-S5SC5T6Z-DL2E3K8-.js} +248 -139
  183. package/package.json +54 -57
  184. package/src/__mocks__/notebook.ts +1 -0
  185. package/src/__mocks__/requests.ts +7 -0
  186. package/src/__tests__/__snapshots__/CellStatus.test.tsx.snap +12 -12
  187. package/src/__tests__/fixtures/pair-preview.ts +19 -0
  188. package/src/__tests__/mount.test.ts +18 -0
  189. package/src/components/app-config/optional-features.tsx +6 -0
  190. package/src/components/chat/acp/__tests__/agent-panel-initialization.test.tsx +394 -0
  191. package/src/components/chat/acp/__tests__/agent-panel.test.tsx +272 -0
  192. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  193. package/src/components/chat/acp/agent-panel.tsx +115 -74
  194. package/src/components/chat/acp/state.ts +1 -1
  195. package/src/components/data-table/__tests__/filters.test.ts +21 -21
  196. package/src/components/data-table/charts/__tests__/spec.test.ts +5 -10
  197. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +2 -6
  198. package/src/components/data-table/column-summary/chart-spec-model.tsx +1 -1
  199. package/src/components/data-table/columns.tsx +64 -67
  200. package/src/components/editor/RecoveryButton.tsx +1 -0
  201. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +36 -2
  202. package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +115 -0
  203. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +218 -0
  204. package/src/components/editor/actions/pair-with-agent-commands.ts +46 -9
  205. package/src/components/editor/actions/pair-with-agent-modal.tsx +49 -23
  206. package/src/components/editor/ai/add-cell-with-ai.tsx +1 -0
  207. package/src/components/editor/ai/ai-completion-editor.tsx +3 -1
  208. package/src/components/editor/alerts/__tests__/connection-notice.test.tsx +186 -0
  209. package/src/components/editor/alerts/__tests__/startup-progress.test.tsx +185 -0
  210. package/src/components/editor/alerts/connecting-alert.tsx +1 -66
  211. package/src/components/editor/alerts/connection-notice.tsx +186 -0
  212. package/src/components/editor/alerts/startup-output.tsx +173 -0
  213. package/src/components/editor/alerts/startup-progress.tsx +204 -0
  214. package/src/components/editor/app-container.tsx +9 -3
  215. package/src/components/editor/boundary/ErrorBoundary.tsx +38 -16
  216. package/src/components/editor/boundary/__tests__/ErrorBoundary.test.tsx +311 -0
  217. package/src/components/editor/cell/__tests__/toolbar.test.tsx +73 -0
  218. package/src/components/editor/cell/code/__tests__/rtc-editor-config.test.ts +50 -0
  219. package/src/components/editor/cell/code/cell-editor.tsx +41 -42
  220. package/src/components/editor/cell/toolbar.tsx +11 -0
  221. package/src/components/editor/chrome/components/__tests__/feedback-button.test.tsx +94 -0
  222. package/src/components/editor/chrome/components/feedback-button.tsx +63 -42
  223. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +164 -8
  224. package/src/components/editor/chrome/panels/__tests__/sandbox-details.test.tsx +358 -0
  225. package/src/components/editor/chrome/panels/__tests__/sandbox-recovery.test.tsx +168 -0
  226. package/src/components/editor/chrome/panels/packages-panel.tsx +113 -175
  227. package/src/components/editor/chrome/panels/sandbox-controller.tsx +106 -0
  228. package/src/components/editor/chrome/panels/sandbox-details-state.ts +38 -0
  229. package/src/components/editor/chrome/panels/sandbox-panel.tsx +230 -0
  230. package/src/components/editor/chrome/panels/sandbox-toggle.tsx +52 -0
  231. package/src/components/editor/chrome/wrapper/app-chrome.tsx +7 -0
  232. package/src/components/editor/chrome/wrapper/footer-items/backend-status.tsx +3 -1
  233. package/src/components/editor/code/__tests__/readonly-diff.test.tsx +65 -0
  234. package/src/components/editor/code/readonly-diff.tsx +1 -1
  235. package/src/components/editor/connections/database/__tests__/__snapshots__/as-code.test.ts.snap +98 -98
  236. package/src/components/editor/connections/database/__tests__/as-code.test.ts +11 -5
  237. package/src/components/editor/dynamic-favicon.tsx +17 -10
  238. package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +13 -11
  239. package/src/components/editor/file-tree/__tests__/tree-actions.test.tsx +54 -0
  240. package/src/components/editor/file-tree/tree-actions.tsx +11 -14
  241. package/src/components/editor/header/__tests__/status.test.tsx +8 -20
  242. package/src/components/editor/header/filename-input.tsx +2 -0
  243. package/src/components/editor/header/status.tsx +5 -3
  244. package/src/components/editor/navigation/clipboard.ts +1 -0
  245. package/src/components/editor/notebook-cell.tsx +4 -2
  246. package/src/components/editor/output/JsonOutput.tsx +182 -632
  247. package/src/components/editor/output/Outputs.css +49 -17
  248. package/src/components/editor/output/__tests__/JsonOutput-interactions.test.tsx +254 -0
  249. package/src/components/editor/output/__tests__/JsonOutput-mimetype.test.tsx +41 -46
  250. package/src/components/editor/output/__tests__/{json-output.test.ts → json-output-formatting.test.ts} +113 -75
  251. package/src/components/editor/output/__tests__/json-output-pagination.test.ts +252 -0
  252. package/src/components/editor/output/json-output/formatting.ts +167 -0
  253. package/src/components/editor/output/json-output/pagination.ts +155 -0
  254. package/src/components/editor/output/json-output/renderers.tsx +196 -0
  255. package/src/components/editor/output/json-output/themes.ts +52 -0
  256. package/src/components/editor/package-alert.tsx +112 -66
  257. package/src/components/editor/renderers/cell-array.tsx +1 -5
  258. package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +3 -7
  259. package/src/components/slides/slide-form.tsx +1 -0
  260. package/src/components/slides/slide-notes-editor.tsx +1 -0
  261. package/src/components/tracing/tracing-spec.ts +1 -1
  262. package/src/components/ui/__tests__/badge.test.ts +6 -4
  263. package/src/components/ui/use-toast.ts +8 -0
  264. package/src/core/MarimoApp.tsx +0 -2
  265. package/src/core/ai/context/providers/__tests__/error.test.ts +16 -22
  266. package/src/core/alerts/__tests__/environment.test.ts +183 -0
  267. package/src/core/alerts/__tests__/state.test.tsx +240 -0
  268. package/src/core/alerts/environment.ts +57 -0
  269. package/src/core/alerts/state.ts +155 -99
  270. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +296 -259
  271. package/src/core/cells/__tests__/apply-transaction.test.ts +34 -0
  272. package/src/core/cells/__tests__/cell.test.ts +61 -0
  273. package/src/core/cells/__tests__/cells.test.ts +182 -37
  274. package/src/core/cells/__tests__/document-changes.test.ts +39 -1
  275. package/src/core/cells/__tests__/outputs.test.tsx +117 -0
  276. package/src/core/cells/__tests__/pending-delete-service.test.tsx +4 -3
  277. package/src/core/cells/__tests__/session.test.ts +7 -0
  278. package/src/core/cells/cell.ts +9 -1
  279. package/src/core/cells/cells.ts +13 -18
  280. package/src/core/cells/document-changes.ts +11 -1
  281. package/src/core/cells/outputs.ts +66 -9
  282. package/src/core/cells/pending-delete-service.ts +12 -16
  283. package/src/core/cells/session.ts +2 -0
  284. package/src/core/cells/types.ts +2 -0
  285. package/src/core/codemirror/find-replace/__tests__/navigate.test.ts +8 -16
  286. package/src/core/codemirror/language/__tests__/sql.test.ts +70 -2
  287. package/src/core/codemirror/language/embedded/__tests__/embedded-python.test.ts +4 -8
  288. package/src/core/codemirror/language/languages/sql/sql.ts +16 -1
  289. package/src/core/codemirror/rtc/extension.ts +5 -2
  290. package/src/core/config/pair.ts +19 -0
  291. package/src/core/datasets/__tests__/data-source.test.ts +2 -0
  292. package/src/core/datasets/data-source-connections.ts +15 -10
  293. package/src/core/edit-app.tsx +8 -5
  294. package/src/core/errors/state.ts +1 -2
  295. package/src/core/export/__tests__/hooks.test.ts +1 -6
  296. package/src/core/islands/bootstrap.ts +3 -1
  297. package/src/core/islands/bridge.ts +3 -0
  298. package/src/core/islands/worker-factory.ts +2 -12
  299. package/src/core/kernel/messages.ts +2 -2
  300. package/src/core/network/__tests__/requests-network.test.ts +4 -3
  301. package/src/core/network/connection-notice.ts +117 -0
  302. package/src/core/network/connection.ts +19 -0
  303. package/src/core/network/requests-lazy.ts +3 -0
  304. package/src/core/network/requests-network.ts +12 -0
  305. package/src/core/network/requests-static.ts +3 -0
  306. package/src/core/network/requests-toasting.tsx +3 -0
  307. package/src/core/network/resolve.ts +2 -1
  308. package/src/core/network/types.ts +7 -0
  309. package/src/core/network/useConnectionNotice.ts +53 -0
  310. package/src/core/packages/__tests__/usePackageAction.test.tsx +129 -0
  311. package/src/core/packages/__tests__/useSandboxController.test.tsx +246 -0
  312. package/src/core/packages/package-data.ts +35 -0
  313. package/src/core/packages/sandbox-state.ts +67 -0
  314. package/src/core/packages/toast-components.tsx +11 -0
  315. package/src/core/packages/useInstallPackage.ts +1 -1
  316. package/src/core/packages/usePackageAction.ts +51 -0
  317. package/src/core/packages/usePackageDependencies.ts +22 -0
  318. package/src/core/packages/useSandboxController.ts +208 -0
  319. package/src/core/run-app.tsx +2 -0
  320. package/src/core/static/__tests__/files.test.ts +12 -30
  321. package/src/core/wasm/__tests__/bootstrap.test.ts +63 -0
  322. package/src/core/wasm/__tests__/bridge.test.ts +13 -4
  323. package/src/core/wasm/__tests__/fs.test.ts +22 -0
  324. package/src/core/wasm/__tests__/runtime-config.test.ts +65 -0
  325. package/src/core/wasm/bridge.ts +35 -23
  326. package/src/core/wasm/rpc.ts +2 -0
  327. package/src/core/wasm/runtime-config.ts +31 -0
  328. package/src/core/wasm/worker/bootstrap.ts +21 -11
  329. package/src/core/wasm/worker/constants.ts +1 -0
  330. package/src/core/wasm/worker/fs.ts +4 -0
  331. package/src/core/wasm/worker/getController.ts +3 -1
  332. package/src/core/wasm/worker/save-worker.ts +10 -9
  333. package/src/core/wasm/worker/types.ts +6 -4
  334. package/src/core/wasm/worker/worker.ts +9 -9
  335. package/src/core/websocket/__tests__/useMarimoKernelConnection.hook.test.tsx +404 -3
  336. package/src/core/websocket/__tests__/useMarimoKernelConnection.test.ts +8 -6
  337. package/src/core/websocket/transports/transport.ts +1 -1
  338. package/src/core/websocket/transports/ws.ts +1 -1
  339. package/src/core/websocket/types.ts +11 -1
  340. package/src/core/websocket/useMarimoKernelConnection.tsx +63 -16
  341. package/src/hooks/useDebounce.ts +2 -0
  342. package/src/mount.tsx +3 -0
  343. package/src/plugins/core/BadPlugin.tsx +3 -14
  344. package/src/plugins/core/RenderHTML.tsx +22 -2
  345. package/src/plugins/core/__test__/RenderHTML.test.ts +187 -0
  346. package/src/plugins/core/registerReactComponent.tsx +0 -1
  347. package/src/plugins/impl/FileBrowserPlugin.tsx +10 -4
  348. package/src/plugins/impl/FormPlugin.tsx +2 -0
  349. package/src/plugins/impl/MatrixPlugin.tsx +2 -0
  350. package/src/plugins/impl/RangeSliderPlugin.tsx +2 -0
  351. package/src/plugins/impl/SliderPlugin.tsx +2 -0
  352. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +50 -1
  353. package/src/plugins/impl/data-editor/__tests__/glide-data-editor.test.tsx +23 -18
  354. package/src/plugins/impl/data-frames/panel.tsx +1 -0
  355. package/src/plugins/impl/image-comparison/ImageComparisonComponent.tsx +1 -1
  356. package/src/plugins/impl/image-comparison/__tests__/ImageComparisonComponent.test.tsx +15 -0
  357. package/src/plugins/impl/vega/__tests__/make-selectable.test.ts +42 -48
  358. package/src/plugins/layout/AccordionPlugin.tsx +7 -2
  359. package/src/plugins/layout/JsonOutputPlugin.tsx +7 -10
  360. package/src/plugins/layout/__test__/AccordionPlugin.test.tsx +66 -0
  361. package/src/stories/sandbox.stories.tsx +488 -0
  362. package/src/utils/__tests__/path.test.ts +6 -5
  363. package/src/utils/__tests__/worker.test.ts +81 -0
  364. package/src/utils/worker.ts +22 -0
  365. package/dist/ErrorBoundary-CR-ZQN_c.js +0 -125
  366. package/dist/_baseMerge-Dv8bBloN.js +0 -73
  367. package/dist/_initCloneObject---vediN2.js +0 -95
  368. package/dist/arc-DOVLrCPd.js +0 -82
  369. package/dist/architecture-TIHT7OUA-MiJpPpQW.js +0 -3
  370. package/dist/array-CApNbSU9.js +0 -6
  371. package/dist/assets/__vite-browser-external-BdKGjRZ4.js +0 -1
  372. package/dist/assets/worker-DlIBfp9S.js +0 -98
  373. package/dist/blockDiagram-VBNYF7ZC-Bc--I65C.js +0 -3158
  374. package/dist/chat-ui-BSwr409J.js +0 -8455
  375. package/dist/chunk-3NCLNEKW-BIp_lB_4.js +0 -14
  376. package/dist/chunk-5FCAYU7R-C_HO9A1J.js +0 -23
  377. package/dist/chunk-5HE753X5-Bixm_MV-.js +0 -38
  378. package/dist/chunk-5JV3BV7I-CH5J3iZY.js +0 -54
  379. package/dist/chunk-5TONJI2A-DG6gDiSS.js +0 -62
  380. package/dist/chunk-7BUUIJ7U-5afmIPeo.js +0 -82
  381. package/dist/chunk-BIQX33UG-7TMBUf89.js +0 -23
  382. package/dist/chunk-CQNSW5MT-Cnqqsxt-.js +0 -38
  383. package/dist/chunk-CYSBUYHQ-BAbymFHR.js +0 -23
  384. package/dist/chunk-EMLP6XTP-Bsz-QL77.js +0 -23
  385. package/dist/chunk-J7OUQ5F2-CM_meqZH.js +0 -57
  386. package/dist/chunk-JG7HCLWE-DUnCDgPk.js +0 -62
  387. package/dist/chunk-MOZMSUNE-BG6IHvzT.js +0 -35
  388. package/dist/chunk-OSBZ3O6U-CYwj_bvR.js +0 -23
  389. package/dist/chunk-QBLGF6JB-B4vJ4j8J.js +0 -23
  390. package/dist/chunk-QR6OTTB3-D4wH2ldi.js +0 -3738
  391. package/dist/chunk-R7FJI6CG-Blf8FcmZ.js +0 -64
  392. package/dist/chunk-RYQCIY6F-BWbIlBIH.js +0 -241
  393. package/dist/chunk-U6XO7XAA-COw445DS.js +0 -63
  394. package/dist/chunk-UBXNYLIW-BHigVU4H.js +0 -32
  395. package/dist/chunk-YOTPTUD7-Dn044Q_d.js +0 -30
  396. package/dist/chunk-ZIRB5QZD-BR4TMfOa.js +0 -1153
  397. package/dist/classDiagram-JCYQIIEL-DmXSLxe_.js +0 -30
  398. package/dist/classDiagram-v2-OCEON4UE-CooqVexA.js +0 -30
  399. package/dist/click-outside-container-D_URcm3D.js +0 -167
  400. package/dist/code-block-37QAKDTI-CQrzBOr-.js +0 -107
  401. package/dist/copy-CD7S58CY.js +0 -141
  402. package/dist/cynefin-VYW2F7L2-CeHAxOEX.js +0 -3
  403. package/dist/dagre-VZM6K2ZE-CJggWPTJ.js +0 -328
  404. package/dist/dist-3_o3T78I.js +0 -1360
  405. package/dist/dist-ABQjhuek.js +0 -64
  406. package/dist/dist-CO5R6VMi.js +0 -538
  407. package/dist/dropdown-menu-DhCfcscX.js +0 -5858
  408. package/dist/emotion-is-prop-valid.esm-M57VsT8j.js +0 -14
  409. package/dist/error-banner-CsXbEEjo.js +0 -611
  410. package/dist/eventmodeling-45OFAUF4-B_lWZ-Pd.js +0 -3
  411. package/dist/flowDiagram-UKHOOZJN-g1AmrZzM.js +0 -17
  412. package/dist/gitGraph-TEB2WS4Q-Dj-eXUph.js +0 -3
  413. package/dist/info-DKCQHKI2-Bhb-pe2e.js +0 -3
  414. package/dist/line-CuKjIQa1.js +0 -34
  415. package/dist/map-BJpOfPlL.js +0 -149
  416. package/dist/math-BXtKgYah.js +0 -10
  417. package/dist/mermaid-4DMBBIKO-Ddp9TgNp.js +0 -2
  418. package/dist/mermaid-Bi23Ibnw.js +0 -1173
  419. package/dist/mermaid-parser.core-Din9FvSi.js +0 -99
  420. package/dist/packet-7NZHBO7P-fbkIIyKE.js +0 -3
  421. package/dist/path-BNHuHZP8.js +0 -87
  422. package/dist/pie-RZYD4A2V-CLRIsxY1.js +0 -3
  423. package/dist/purify.es-CB-YO1Hn.js +0 -774
  424. package/dist/radar-I7S5WNFK-BGzCbgXu.js +0 -3
  425. package/dist/railroad-3IZDKUUU-CvQIIcuU.js +0 -3
  426. package/dist/railroad-abnf-AHOZXSZD-CMwtlNtu.js +0 -3
  427. package/dist/railroad-ebnf-EBAXGLYW-n1QUOAsh.js +0 -3
  428. package/dist/railroad-peg-LSFZ7HO6-PjnrpmCs.js +0 -3
  429. package/dist/schemas-DzK6U94N.js +0 -6057
  430. package/dist/stateDiagram-v2-UEYNNEHI-BTRUs8Il.js +0 -30
  431. package/dist/step-CKczO72Q.js +0 -670
  432. package/dist/swimlanesDiagram-ULZ7WXOC-NBmgBl9G.js +0 -31
  433. package/dist/timer-DpDyj9G0.js +0 -596
  434. package/dist/tooltip-Dt77sBeT.js +0 -390
  435. package/dist/treeView-QDETBFTQ-DU8LwHuf.js +0 -3
  436. package/dist/treemap-6X3UGDF4-DUk4-k_O.js +0 -3
  437. package/dist/useTheme-CVJ0UOf2.js +0 -841
  438. package/dist/vega-loader.browser-DEggZzni.js +0 -1083
  439. package/dist/wardley-OPB4EBWU-Db-AaGas.js +0 -3
  440. package/src/components/editor/output/EmotionCacheProvider.tsx +0 -32
@@ -0,0 +1,311 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
4
+ import type { PropsWithChildren, ReactNode } from "react";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
+ import { MockRequestClient } from "@/__mocks__/requests";
7
+ import { ModalProvider } from "@/components/modal/ImperativeModal";
8
+ import { TooltipProvider } from "@/components/ui/tooltip";
9
+ import { clearToasts } from "@/components/ui/use-toast";
10
+ import { initialNotebookState, notebookAtom } from "@/core/cells/cells";
11
+ import { viewStateAtom } from "@/core/mode";
12
+ import { connectionAtom } from "@/core/network/connection";
13
+ import { requestClientAtom } from "@/core/network/requests";
14
+ import type { EnvironmentInfo } from "@/core/network/types";
15
+ import { filenameAtom } from "@/core/saving/file-state";
16
+ import { store } from "@/core/state/jotai";
17
+ import { WebSocketState } from "@/core/websocket/types";
18
+ import { ErrorBoundary } from "../ErrorBoundary";
19
+
20
+ vi.mock("@/utils/copy", () => ({
21
+ copyToClipboard: vi.fn().mockResolvedValue(undefined),
22
+ }));
23
+
24
+ const environment: EnvironmentInfo = {
25
+ marimo: "1.2.3",
26
+ editable: false,
27
+ location: "~/.venv/site-packages/marimo",
28
+ OS: "Darwin",
29
+ "OS Version": "25.0",
30
+ Processor: "arm",
31
+ "Python Version": "3.12.9",
32
+ Locale: "en_US",
33
+ Binaries: { Browser: "chrome 140", Node: "v22", uv: "0.11" },
34
+ Dependencies: { click: "8.4.2" },
35
+ "Optional Dependencies": { pandas: "3.0.0" },
36
+ "Experimental Flags": {},
37
+ };
38
+
39
+ const CRASH_MESSAGE = "cell output crashed";
40
+
41
+ function CrashingChild(): never {
42
+ throw new Error(CRASH_MESSAGE);
43
+ }
44
+
45
+ function RecoverableChild({ crash }: { crash: boolean }) {
46
+ if (crash) {
47
+ throw new Error(CRASH_MESSAGE);
48
+ }
49
+ return <p>recovered child</p>;
50
+ }
51
+
52
+ function DescendantProviders({ children }: PropsWithChildren) {
53
+ return (
54
+ <TooltipProvider>
55
+ <ModalProvider>{children}</ModalProvider>
56
+ </TooltipProvider>
57
+ );
58
+ }
59
+
60
+ function isExpectedReactExceptionOutput(args: unknown[]): boolean {
61
+ return args.some((arg) => {
62
+ if (arg instanceof Error) {
63
+ return arg.message.includes(CRASH_MESSAGE);
64
+ }
65
+ return (
66
+ typeof arg === "string" &&
67
+ (arg.includes(CRASH_MESSAGE) ||
68
+ arg.includes("The above error occurred") ||
69
+ arg.includes("React will try to recreate this component tree"))
70
+ );
71
+ });
72
+ }
73
+
74
+ function suppressExpectedReactException() {
75
+ return vi.spyOn(console, "error").mockImplementation((...args) => {
76
+ if (isExpectedReactExceptionOutput(args)) {
77
+ return;
78
+ }
79
+ throw new Error(`Unexpected console.error: ${args.map(String).join(" ")}`);
80
+ });
81
+ }
82
+
83
+ let consoleError: ReturnType<typeof vi.spyOn> | undefined;
84
+
85
+ function renderCrashedBoundary(child: ReactNode = <CrashingChild />) {
86
+ consoleError?.mockRestore();
87
+ consoleError = suppressExpectedReactException();
88
+ return render(<ErrorBoundary>{child}</ErrorBoundary>);
89
+ }
90
+
91
+ function setRequestClient(
92
+ overrides?: Parameters<typeof MockRequestClient.create>[0],
93
+ ) {
94
+ const client = MockRequestClient.create(overrides);
95
+ store.set(requestClientAtom, client);
96
+ return client;
97
+ }
98
+
99
+ async function openReportDialog() {
100
+ const trigger = screen.getByRole("button", { name: "Report an issue" });
101
+ trigger.focus();
102
+ fireEvent.click(trigger);
103
+ return screen.findByRole("dialog");
104
+ }
105
+
106
+ async function expectReportButtonFocused() {
107
+ await waitFor(() => {
108
+ expect(
109
+ screen.getByRole("button", { name: "Report an issue" }),
110
+ ).toHaveFocus();
111
+ });
112
+ }
113
+
114
+ async function closeReportDialog(method: "escape" | "close") {
115
+ const dialog = await screen.findByRole("dialog");
116
+ if (method === "escape") {
117
+ fireEvent.keyDown(dialog, { key: "Escape" });
118
+ } else {
119
+ fireEvent.click(screen.getByRole("button", { name: "Close" }));
120
+ }
121
+ await waitFor(() => {
122
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
123
+ });
124
+ await expectReportButtonFocused();
125
+ }
126
+
127
+ async function expectRealReportModal() {
128
+ const dialog = await openReportDialog();
129
+ expect(dialog).toHaveTextContent("Report an issue");
130
+ expect(screen.getByRole("link", { name: "Open GitHub issue" })).toBeVisible();
131
+ expect(
132
+ screen.getByRole("checkbox", { name: "Include errors" }),
133
+ ).toBeVisible();
134
+ expect(
135
+ screen.getByRole("checkbox", { name: "Include notebook code" }),
136
+ ).toBeVisible();
137
+ await screen.findByText("Environment details");
138
+ return dialog;
139
+ }
140
+
141
+ function resetSharedState() {
142
+ localStorage.clear();
143
+ clearToasts();
144
+ store.set(requestClientAtom, null);
145
+ store.set(notebookAtom, initialNotebookState());
146
+ store.set(viewStateAtom, { mode: "edit", cellAnchor: null });
147
+ store.set(connectionAtom, { state: WebSocketState.OPEN });
148
+ store.set(filenameAtom, "/project/example.py");
149
+ }
150
+
151
+ describe("ErrorBoundary report dialog", () => {
152
+ beforeEach(() => {
153
+ resetSharedState();
154
+ });
155
+
156
+ afterEach(() => {
157
+ consoleError?.mockRestore();
158
+ consoleError = undefined;
159
+ resetSharedState();
160
+ });
161
+
162
+ it("opens the report modal after a child render exception", async () => {
163
+ const { getEnvironmentInfo } = setRequestClient({
164
+ getEnvironmentInfo: vi.fn().mockResolvedValue(environment),
165
+ });
166
+
167
+ renderCrashedBoundary();
168
+
169
+ expect(screen.getByText("Something went wrong")).toBeVisible();
170
+ expect(screen.getByText("cell output crashed")).toBeVisible();
171
+ expect(screen.getByTestId("reset-error-boundary-button")).toBeVisible();
172
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
173
+ expect(getEnvironmentInfo).not.toHaveBeenCalled();
174
+
175
+ await expectRealReportModal();
176
+ await waitFor(() => expect(getEnvironmentInfo).toHaveBeenCalledOnce());
177
+ });
178
+
179
+ it("still opens one report dialog after descendant providers disappear", async () => {
180
+ const { getEnvironmentInfo } = setRequestClient({
181
+ getEnvironmentInfo: vi.fn().mockResolvedValue(environment),
182
+ });
183
+
184
+ renderCrashedBoundary(
185
+ <DescendantProviders>
186
+ <CrashingChild />
187
+ </DescendantProviders>,
188
+ );
189
+
190
+ await expectRealReportModal();
191
+ expect(screen.getAllByRole("dialog")).toHaveLength(1);
192
+ await waitFor(() => expect(getEnvironmentInfo).toHaveBeenCalledOnce());
193
+ });
194
+
195
+ it("shows partial diagnostics when the request client is missing", async () => {
196
+ renderCrashedBoundary();
197
+
198
+ await openReportDialog();
199
+ await screen.findByText("Environment details");
200
+
201
+ expect(
202
+ screen.getByText("Server environment information unavailable"),
203
+ ).toBeVisible();
204
+ expect(
205
+ screen.queryByText("Loading environment details…"),
206
+ ).not.toBeInTheDocument();
207
+ expect(
208
+ screen.queryByRole("button", { name: "Retry" }),
209
+ ).not.toBeInTheDocument();
210
+ expect(
211
+ screen.getByRole("checkbox", { name: "Include notebook code" }),
212
+ ).toBeDisabled();
213
+ expect(screen.getByText("Notebook source is unavailable.")).toBeVisible();
214
+ const link = screen.getByRole("link", { name: "Open GitHub issue" });
215
+ expect(link.getAttribute("href") ?? "").toContain("&env=");
216
+ });
217
+
218
+ it("does not request environment or source while the report dialog is closed", async () => {
219
+ localStorage.setItem(
220
+ "marimo:issue-report:include-code",
221
+ JSON.stringify(true),
222
+ );
223
+ const { getEnvironmentInfo, readCode } = setRequestClient({
224
+ getEnvironmentInfo: vi.fn().mockResolvedValue(environment),
225
+ readCode: vi.fn().mockResolvedValue({ contents: "import marimo" }),
226
+ });
227
+
228
+ renderCrashedBoundary();
229
+
230
+ expect(screen.getByText("Something went wrong")).toBeVisible();
231
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
232
+ expect(getEnvironmentInfo).not.toHaveBeenCalled();
233
+ expect(readCode).not.toHaveBeenCalled();
234
+ });
235
+
236
+ it("restores focus to the report button after closing with Escape", async () => {
237
+ setRequestClient({
238
+ getEnvironmentInfo: vi.fn().mockResolvedValue(environment),
239
+ });
240
+ renderCrashedBoundary();
241
+ await openReportDialog();
242
+ await closeReportDialog("escape");
243
+ expect(screen.getByText(CRASH_MESSAGE)).toBeVisible();
244
+ });
245
+
246
+ it("restores focus to the report button after closing with the close control", async () => {
247
+ setRequestClient({
248
+ getEnvironmentInfo: vi.fn().mockResolvedValue(environment),
249
+ });
250
+ renderCrashedBoundary();
251
+ await openReportDialog();
252
+ await closeReportDialog("close");
253
+ expect(screen.getByText(CRASH_MESSAGE)).toBeVisible();
254
+ });
255
+
256
+ it("reopens the report dialog while the original error remains", async () => {
257
+ setRequestClient({
258
+ getEnvironmentInfo: vi.fn().mockResolvedValue(environment),
259
+ });
260
+ renderCrashedBoundary();
261
+ await openReportDialog();
262
+ await closeReportDialog("escape");
263
+ expect(screen.getByText(CRASH_MESSAGE)).toBeVisible();
264
+ await expectRealReportModal();
265
+ expect(screen.getByText(CRASH_MESSAGE)).toBeVisible();
266
+ });
267
+
268
+ it("restores the child after try again once the failure is cleared", () => {
269
+ setRequestClient({
270
+ getEnvironmentInfo: vi.fn().mockResolvedValue(environment),
271
+ });
272
+ const view = renderCrashedBoundary(<RecoverableChild crash={true} />);
273
+ expect(screen.getByText("Something went wrong")).toBeVisible();
274
+
275
+ view.rerender(
276
+ <ErrorBoundary>
277
+ <RecoverableChild crash={false} />
278
+ </ErrorBoundary>,
279
+ );
280
+ fireEvent.click(screen.getByTestId("reset-error-boundary-button"));
281
+
282
+ expect(screen.getByText("recovered child")).toBeVisible();
283
+ expect(screen.queryByText("Something went wrong")).not.toBeInTheDocument();
284
+ });
285
+
286
+ it("does not read notebook source when opened with fresh inclusion preferences", async () => {
287
+ const { readCode } = setRequestClient({
288
+ getEnvironmentInfo: vi.fn().mockResolvedValue(environment),
289
+ readCode: vi.fn().mockResolvedValue({ contents: "import marimo" }),
290
+ });
291
+
292
+ renderCrashedBoundary();
293
+ await expectRealReportModal();
294
+
295
+ expect(readCode).not.toHaveBeenCalled();
296
+ });
297
+
298
+ it("renders the copy toast after the ancestor toaster is gone", async () => {
299
+ setRequestClient({
300
+ getEnvironmentInfo: vi.fn().mockResolvedValue(environment),
301
+ });
302
+ renderCrashedBoundary();
303
+ await expectRealReportModal();
304
+
305
+ fireEvent.click(
306
+ screen.getByRole("button", { name: "Copy environment JSON" }),
307
+ );
308
+
309
+ expect(await screen.findByText("Environment details copied")).toBeVisible();
310
+ });
311
+ });
@@ -0,0 +1,73 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { fireEvent, render, screen } from "@testing-library/react";
4
+ import { describe, expect, it, vi } from "vitest";
5
+ import { ToolbarItem } from "../toolbar";
6
+
7
+ describe("ToolbarItem keyboard activation", () => {
8
+ it.each(["Enter", " "])(
9
+ "keeps native %s activation out of cell navigation and calls the consumer handler",
10
+ (key) => {
11
+ const onCellKeyDown = vi.fn();
12
+ const onKeyDown = vi.fn();
13
+ render(
14
+ <div onKeyDown={onCellKeyDown}>
15
+ <ToolbarItem tooltip={null} onKeyDown={onKeyDown}>
16
+ Run
17
+ </ToolbarItem>
18
+ </div>,
19
+ );
20
+ const button = screen.getByRole("button", { name: "Run" });
21
+
22
+ expect(fireEvent.keyDown(button, { key })).toBe(true);
23
+ expect(onCellKeyDown).not.toHaveBeenCalled();
24
+ expect(onKeyDown).toHaveBeenCalledOnce();
25
+ expect(onKeyDown.mock.calls[0][0].defaultPrevented).toBe(false);
26
+ },
27
+ );
28
+
29
+ it.each([
30
+ { key: "Enter", ctrlKey: true },
31
+ { key: "Enter", metaKey: true },
32
+ { key: "Enter", shiftKey: true },
33
+ { key: "Enter", altKey: true },
34
+ { key: " ", ctrlKey: true },
35
+ { key: "ArrowLeft" },
36
+ { key: "ArrowRight" },
37
+ { key: "Tab" },
38
+ { key: "Escape" },
39
+ ])("preserves propagation for %j", (event) => {
40
+ const onCellKeyDown = vi.fn();
41
+ render(
42
+ <div onKeyDown={onCellKeyDown}>
43
+ <ToolbarItem tooltip={null}>Run</ToolbarItem>
44
+ </div>,
45
+ );
46
+
47
+ expect(fireEvent.keyDown(screen.getByRole("button"), event)).toBe(true);
48
+ expect(onCellKeyDown).toHaveBeenCalledOnce();
49
+ });
50
+
51
+ it("does not submit forms and preserves disabled and mouse-focus behavior", () => {
52
+ const onClick = vi.fn();
53
+ const { rerender } = render(
54
+ <ToolbarItem tooltip={null} onClick={onClick}>
55
+ Run
56
+ </ToolbarItem>,
57
+ );
58
+ const button = screen.getByRole("button");
59
+ expect(button).toHaveAttribute("type", "button");
60
+ expect(fireEvent.mouseDown(button)).toBe(false);
61
+ fireEvent.click(button);
62
+ expect(onClick).toHaveBeenCalledOnce();
63
+
64
+ rerender(
65
+ <ToolbarItem tooltip={null} onClick={onClick} disabled={true}>
66
+ Run
67
+ </ToolbarItem>,
68
+ );
69
+ expect(button).toBeDisabled();
70
+ fireEvent.click(button);
71
+ expect(onClick).toHaveBeenCalledOnce();
72
+ });
73
+ });
@@ -0,0 +1,50 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { EditorState } from "@codemirror/state";
4
+ import { describe, expect, it } from "vitest";
5
+ import { composeRtcEditorConfig } from "../cell-editor";
6
+
7
+ describe("composeRtcEditorConfig", () => {
8
+ it("does not accumulate RTC extensions across reconfigurations", () => {
9
+ const baseExtension = EditorState.readOnly.of(false);
10
+ const baseExtensions = [baseExtension];
11
+ const firstRtc = {
12
+ code: "first RTC code",
13
+ extension: EditorState.tabSize.of(2),
14
+ };
15
+ const secondRtc = {
16
+ code: "second RTC code",
17
+ extension: EditorState.tabSize.of(4),
18
+ };
19
+
20
+ const firstConfig = composeRtcEditorConfig(
21
+ "local code",
22
+ baseExtensions,
23
+ firstRtc,
24
+ );
25
+ const secondConfig = composeRtcEditorConfig(
26
+ "local code",
27
+ baseExtensions,
28
+ secondRtc,
29
+ );
30
+
31
+ expect(baseExtensions).toEqual([baseExtension]);
32
+ expect(firstConfig).toEqual({
33
+ code: firstRtc.code,
34
+ extensions: [baseExtension, firstRtc.extension],
35
+ });
36
+ expect(secondConfig).toEqual({
37
+ code: secondRtc.code,
38
+ extensions: [baseExtension, secondRtc.extension],
39
+ });
40
+ });
41
+
42
+ it("reuses the base configuration when RTC is disabled", () => {
43
+ const extensions = [EditorState.readOnly.of(false)];
44
+
45
+ const config = composeRtcEditorConfig("local code", extensions, undefined);
46
+
47
+ expect(config).toEqual({ code: "local code", extensions });
48
+ expect(config.extensions).toBe(extensions);
49
+ });
50
+ });
@@ -1,6 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  import { historyField } from "@codemirror/commands";
3
- import { EditorState, StateEffect } from "@codemirror/state";
3
+ import { EditorState, type Extension, StateEffect } from "@codemirror/state";
4
4
  import { EditorView, ViewPlugin } from "@codemirror/view";
5
5
  import { useIntersectionObserver } from "@uidotdev/usehooks";
6
6
  import { useAtom, useAtomValue } from "jotai";
@@ -95,6 +95,21 @@ export interface CellEditorProps
95
95
  tooltipParentSelector?: string;
96
96
  }
97
97
 
98
+ export function composeRtcEditorConfig(
99
+ code: string,
100
+ extensions: Extension[],
101
+ rtc: ReturnType<typeof realTimeCollaboration> | undefined,
102
+ ): { code: string; extensions: Extension[] } {
103
+ if (!rtc) {
104
+ return { code, extensions };
105
+ }
106
+
107
+ return {
108
+ code: rtc.code,
109
+ extensions: [...extensions, rtc.extension],
110
+ };
111
+ }
112
+
98
113
  const CellEditorInternal = ({
99
114
  theme,
100
115
  showPlaceholder,
@@ -343,27 +358,31 @@ const CellEditorInternal = ({
343
358
  ]);
344
359
 
345
360
  const rtcEnabled = isRtcEnabled() && canUseRtc(cellId);
346
- const handleInitializeEditor = useEvent(() => {
347
- // If rtc is enabled, use collaborative editing
348
- if (rtcEnabled) {
349
- const rtc = realTimeCollaboration(
350
- cellId,
351
- (code) => {
352
- // It's not really a formatting change,
353
- // but this means it won't be marked as stale
354
- cellActions.updateCellCode({ cellId, code, formattingChange: true });
355
- },
356
- code,
357
- );
358
- extensions.push(rtc.extension);
359
- code = rtc.code;
361
+ const createEditorConfig = (initialCode?: string) => {
362
+ if (!rtcEnabled) {
363
+ return composeRtcEditorConfig(code, extensions, undefined);
360
364
  }
361
365
 
366
+ const rtc = realTimeCollaboration(
367
+ cellId,
368
+ (code) => {
369
+ // It's not really a formatting change,
370
+ // but this means it won't be marked as stale
371
+ cellActions.updateCellCode({ cellId, code, formattingChange: true });
372
+ },
373
+ initialCode,
374
+ );
375
+ return composeRtcEditorConfig(code, extensions, rtc);
376
+ };
377
+
378
+ const handleInitializeEditor = useEvent(() => {
379
+ const editorConfig = createEditorConfig(code);
380
+
362
381
  // Create a new editor
363
382
  const ev = new EditorView({
364
383
  state: EditorState.create({
365
- doc: code,
366
- extensions: extensions,
384
+ doc: editorConfig.code,
385
+ extensions: editorConfig.extensions,
367
386
  }),
368
387
  });
369
388
  setEditorView(ev);
@@ -377,19 +396,11 @@ const CellEditorInternal = ({
377
396
 
378
397
  const handleReconfigureEditor = useEvent(() => {
379
398
  invariant(editorViewRef.current !== null, "Editor view is not initialized");
380
- // If rtc is enabled, use collaborative editing
381
- if (rtcEnabled) {
382
- const rtc = realTimeCollaboration(cellId, (code) => {
383
- // It's not really a formatting change,
384
- // but this means it won't be marked as stale
385
- cellActions.updateCellCode({ cellId, code, formattingChange: true });
386
- });
387
- extensions.push(rtc.extension);
388
- }
399
+ const editorConfig = createEditorConfig();
389
400
 
390
401
  editorViewRef.current.dispatch({
391
402
  effects: [
392
- StateEffect.reconfigure.of([extensions]),
403
+ StateEffect.reconfigure.of([editorConfig.extensions]),
393
404
  reconfigureLanguageEffect(editorViewRef.current, {
394
405
  completionConfig: userConfig.completion,
395
406
  hotkeysProvider: new OverridingHotkeyProvider(
@@ -406,26 +417,14 @@ const CellEditorInternal = ({
406
417
 
407
418
  const handleDeserializeEditor = useEvent(() => {
408
419
  invariant(serializedEditorState, "Editor view is not initialized");
409
- if (rtcEnabled) {
410
- const rtc = realTimeCollaboration(
411
- cellId,
412
- (code) => {
413
- // It's not really a formatting change,
414
- // but this means it won't be marked as stale
415
- cellActions.updateCellCode({ cellId, code, formattingChange: true });
416
- },
417
- code,
418
- );
419
- extensions.push(rtc.extension);
420
- code = rtc.code;
421
- }
420
+ const editorConfig = createEditorConfig(code);
422
421
 
423
422
  const ev = new EditorView({
424
423
  state: EditorState.fromJSON(
425
424
  serializedEditorState,
426
425
  {
427
- doc: code,
428
- extensions: extensions,
426
+ doc: editorConfig.code,
427
+ extensions: editorConfig.extensions,
429
428
  },
430
429
  { history: historyField },
431
430
  ),
@@ -43,8 +43,19 @@ export const ToolbarItem: React.FC<ToolbarItemProps> = ({
43
43
  }) => {
44
44
  const content = (
45
45
  <button
46
+ type="button"
46
47
  disabled={disabled}
47
48
  {...rest}
49
+ onKeyDown={(evt) => {
50
+ // Preserve native button activation without triggering cell navigation.
51
+ if (
52
+ !Events.hasModifier(evt) &&
53
+ (evt.key === "Enter" || evt.key === " ")
54
+ ) {
55
+ evt.stopPropagation();
56
+ }
57
+ rest.onKeyDown?.(evt);
58
+ }}
48
59
  onClick={(evt) => {
49
60
  if (!disabled) {
50
61
  rest.onClick?.(evt);
@@ -208,4 +208,98 @@ describe("FeedbackModal issue reporting", () => {
208
208
  expect(href).toContain(encodeURIComponent('"marimo": "1.2.3"'));
209
209
  });
210
210
  });
211
+
212
+ it("shows partial environment details when the request client is missing", async () => {
213
+ store.set(requestClientAtom, null);
214
+ render(<FeedbackModal onClose={vi.fn()} />, { wrapper });
215
+
216
+ await screen.findByText("Environment details");
217
+ expect(
218
+ screen.getByText("Server environment information unavailable"),
219
+ ).toBeInTheDocument();
220
+ expect(
221
+ screen.queryByText("Loading environment details…"),
222
+ ).not.toBeInTheDocument();
223
+ expect(
224
+ screen.queryByRole("button", { name: "Retry" }),
225
+ ).not.toBeInTheDocument();
226
+
227
+ const link = screen.getByRole("link", { name: "Open GitHub issue" });
228
+ const href = link.getAttribute("href") ?? "";
229
+ expect(href).toContain("&env=");
230
+ expect(href).toContain(encodeURIComponent("Environment Collection Error"));
231
+
232
+ fireEvent.click(
233
+ screen.getByRole("button", { name: "Copy environment JSON" }),
234
+ );
235
+ await waitFor(() =>
236
+ expect(copyModule.copyToClipboard).toHaveBeenCalledWith(
237
+ expect.stringContaining("Environment Collection Error"),
238
+ ),
239
+ );
240
+ });
241
+
242
+ it("disables source collection when the request client is missing", async () => {
243
+ localStorage.setItem(
244
+ "marimo:issue-report:include-code",
245
+ JSON.stringify(true),
246
+ );
247
+ store.set(connectionAtom, { state: WebSocketState.CONNECTING });
248
+ store.set(requestClientAtom, null);
249
+ render(<FeedbackModal onClose={vi.fn()} />, { wrapper });
250
+
251
+ await screen.findByText("Environment details");
252
+ expect(
253
+ screen.getByRole("checkbox", { name: "Include notebook code" }),
254
+ ).toBeDisabled();
255
+ expect(
256
+ screen.getByText("Notebook source is unavailable."),
257
+ ).toBeInTheDocument();
258
+ expect(
259
+ screen.queryByText("Connect the notebook to include its source."),
260
+ ).not.toBeInTheDocument();
261
+ const link = screen.getByRole("link", { name: "Open GitHub issue" });
262
+ expect(link.getAttribute("href") ?? "").not.toContain("reproduction-code=");
263
+ });
264
+
265
+ it("preserves partial diagnostics when the environment request throws", async () => {
266
+ store.set(
267
+ requestClientAtom,
268
+ MockRequestClient.create({
269
+ getEnvironmentInfo: vi.fn().mockImplementation(() => {
270
+ throw new Error("offline");
271
+ }),
272
+ }),
273
+ );
274
+ render(<FeedbackModal onClose={vi.fn()} />, { wrapper });
275
+
276
+ await screen.findByText("Server environment information unavailable");
277
+ expect(screen.getByRole("button", { name: "Retry" })).toBeInTheDocument();
278
+ expect(
279
+ screen.getByText(/Environment Collection Error/),
280
+ ).toBeInTheDocument();
281
+ });
282
+
283
+ it("replaces partial diagnostics after a successful environment retry", async () => {
284
+ const getEnvironmentInfo = vi
285
+ .fn()
286
+ .mockRejectedValueOnce(new Error("offline"))
287
+ .mockResolvedValueOnce(environment);
288
+ store.set(
289
+ requestClientAtom,
290
+ MockRequestClient.create({ getEnvironmentInfo }),
291
+ );
292
+ render(<FeedbackModal onClose={vi.fn()} />, { wrapper });
293
+
294
+ await screen.findByText("Server environment information unavailable");
295
+ fireEvent.click(screen.getByRole("button", { name: "Retry" }));
296
+
297
+ await screen.findByText(/"marimo": "1.2.3"/);
298
+ expect(
299
+ screen.queryByText("Server environment information unavailable"),
300
+ ).not.toBeInTheDocument();
301
+ expect(
302
+ screen.queryByRole("button", { name: "Retry" }),
303
+ ).not.toBeInTheDocument();
304
+ });
211
305
  });