@marimo-team/islands 0.24.3-dev9 → 0.25.0

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-DnNkbB_U.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-DElzk_hh.js +138 -0
  46. package/dist/{common-Cv-4hMa0.js → common-B4-ih9IA.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-CRH6JQPE.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-cQcMqpEP.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-DYOxfSzI.js} +2143 -1910
  105. package/dist/{glide-utils-NShAPPtn.js → glide-utils-B3rUT5PI.js} +15 -15
  106. package/dist/globals-BOoLHa7X.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-DZp5bEmI.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-C9h2Lubu.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
@@ -1,6 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import { render } from "@testing-library/react";
3
+ import { render, waitFor } from "@testing-library/react";
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
5
  import { useCellErrors } from "@/core/cells/cells";
6
6
  import { DynamicFavicon } from "../dynamic-favicon";
@@ -14,6 +14,10 @@ describe("DynamicFavicon", () => {
14
14
  let favicon: HTMLLinkElement;
15
15
 
16
16
  beforeEach(() => {
17
+ document
18
+ .querySelectorAll("link[rel~='icon']")
19
+ .forEach((element) => element.remove());
20
+
17
21
  // Mock favicon element
18
22
  favicon = document.createElement("link");
19
23
  favicon.rel = "icon";
@@ -28,7 +32,9 @@ describe("DynamicFavicon", () => {
28
32
  });
29
33
 
30
34
  afterEach(() => {
31
- favicon.remove();
35
+ document
36
+ .querySelectorAll("link[rel~='icon']")
37
+ .forEach((element) => element.remove());
32
38
  vi.clearAllMocks();
33
39
  vi.useRealTimers();
34
40
  });
@@ -113,6 +119,34 @@ describe("DynamicFavicon", () => {
113
119
  expect(newFavicon).not.toBeNull();
114
120
  });
115
121
 
122
+ it("recreates a removed favicon when the window regains focus", async () => {
123
+ render(<DynamicFavicon isRunning={false} />);
124
+ favicon.remove();
125
+
126
+ window.dispatchEvent(new Event("focus"));
127
+
128
+ await waitFor(() => {
129
+ const currentFavicon =
130
+ document.querySelector<HTMLLinkElement>("link[rel~='icon']");
131
+ expect(currentFavicon?.href.endsWith("favicon.ico")).toBe(true);
132
+ });
133
+ });
134
+
135
+ it("resets the current favicon after the link is replaced", async () => {
136
+ render(<DynamicFavicon isRunning={false} />);
137
+ const replacement = document.createElement("link");
138
+ replacement.rel = "icon";
139
+ replacement.href = "./replacement.ico";
140
+ favicon.replaceWith(replacement);
141
+
142
+ window.dispatchEvent(new Event("focus"));
143
+
144
+ await waitFor(() => {
145
+ expect(replacement.href.endsWith("favicon.ico")).toBe(true);
146
+ expect(document.querySelector("link[rel~='icon']")).toBe(replacement);
147
+ });
148
+ });
149
+
116
150
  describe("notifications", () => {
117
151
  beforeEach(() => {
118
152
  vi.spyOn(document, "visibilityState", "get").mockReturnValue("hidden");
@@ -10,10 +10,12 @@ import {
10
10
  maskToken,
11
11
  } from "../pair-with-agent-commands";
12
12
  import { shellQuote } from "@/utils/shell";
13
+ import { PAIR_PREVIEW } from "@/__tests__/fixtures/pair-preview";
13
14
 
14
15
  const CONNECTION: ConnectionInfo = {
15
16
  url: "http://localhost:8000",
16
17
  file: "notebooks/example.py",
18
+ session: "s_ab12cd",
17
19
  };
18
20
 
19
21
  const CONNECTION_WITHOUT_FILE: ConnectionInfo = {
@@ -104,6 +106,61 @@ describe("getMarimoCommand", () => {
104
106
  });
105
107
 
106
108
  describe("getTerminalCommand", () => {
109
+ it("uses the preview launcher in production and quotes session-only targets", () => {
110
+ vi.stubEnv("DEV", false);
111
+ try {
112
+ expect(
113
+ getTerminalCommand(
114
+ "claude",
115
+ { url: "http://host:8000?a=1&b=2", session: "s_' {file}" },
116
+ false,
117
+ { ...PAIR_PREVIEW, command: "uvx marimo@latest" },
118
+ ),
119
+ ).toBe(
120
+ String.raw`claude "$(MARIMO_PAIR_NEXT=1 uvx marimo@latest pair prompt \
121
+ --url 'http://host:8000?a=1&b=2' \
122
+ --session 's_'"'"' {file}')"`,
123
+ );
124
+ expect(
125
+ getTerminalCommand(
126
+ "codex",
127
+ CONNECTION_WITHOUT_FILE,
128
+ false,
129
+ PAIR_PREVIEW,
130
+ ),
131
+ ).toBe(
132
+ String.raw`codex "$(MARIMO_PAIR_NEXT=1 uvx marimo@latest pair prompt \
133
+ --url http://localhost:8000)"`,
134
+ );
135
+ } finally {
136
+ vi.unstubAllEnvs();
137
+ }
138
+ });
139
+
140
+ it.each([
141
+ ["claude", "claude"],
142
+ ["codex", "codex"],
143
+ ["opencode", "opencode --prompt"],
144
+ ] as const)(
145
+ "enables the preview inside the %s prompt command",
146
+ (agent, cli) => {
147
+ expect(
148
+ getTerminalCommand(
149
+ agent,
150
+ { ...CONNECTION, session: "s_ab12cd" },
151
+ true,
152
+ PAIR_PREVIEW,
153
+ ),
154
+ ).toBe(
155
+ String.raw`${cli} "$(MARIMO_PAIR_NEXT=1 uvx marimo@latest pair prompt \
156
+ --url http://localhost:8000 \
157
+ --file notebooks/example.py \
158
+ --session s_ab12cd \
159
+ --with-token)"`,
160
+ );
161
+ },
162
+ );
163
+
107
164
  it("includes the url and file for each agent", () => {
108
165
  expect(getTerminalCommand("claude", CONNECTION, false)).toBe(
109
166
  `claude "$(uv run marimo pair prompt --url http://localhost:8000 --file notebooks/example.py --claude)"`,
@@ -169,6 +226,64 @@ describe("getTerminalCommand", () => {
169
226
  });
170
227
 
171
228
  describe("getRawPrompt", () => {
229
+ it("uses supplied templates without reinterpreting inserted values", () => {
230
+ expect(
231
+ getRawPrompt(
232
+ {
233
+ url: "http://host/{file}",
234
+ file: "{session}/it's.py",
235
+ session: "{command}",
236
+ },
237
+ "tok'en {file} $&",
238
+ {
239
+ ...PAIR_PREVIEW,
240
+ command: "custom marimo",
241
+ templates: {
242
+ ...PAIR_PREVIEW.templates,
243
+ prompt: "{command}\n{url}\n{file}{session}{authentication}",
244
+ },
245
+ },
246
+ ),
247
+ ).toBe(
248
+ "custom marimo\nhttp://host/{file}\nFile: {session}/it's.py\nSession: {command}\n" +
249
+ `\n\nFor authenticated Pair commands, set \`export MARIMO_TOKEN='tok'"'"'en {file} $&'\` in the shell that runs marimo.`,
250
+ );
251
+ });
252
+
253
+ it.each([
254
+ [CONNECTION_WITHOUT_FILE, ""],
255
+ [
256
+ { ...CONNECTION_WITHOUT_FILE, session: "s_ab12cd" },
257
+ "Session: s_ab12cd\n",
258
+ ],
259
+ [
260
+ { ...CONNECTION_WITHOUT_FILE, file: "notebook.py" },
261
+ "File: notebook.py\n",
262
+ ],
263
+ ])("omits absent preview fields for %j", (connection, fields) => {
264
+ expect(getRawPrompt(connection, null, PAIR_PREVIEW)).toBe(
265
+ "Pair with me on this running marimo notebook.\n\n" +
266
+ `URL: http://localhost:8000\n${fields}\n` +
267
+ "Run `uvx marimo@latest pair --help` first.\n" +
268
+ "Use `uvx marimo@latest` for all marimo commands.\n\n" +
269
+ "Once connected, send a fun toast using `mo.status.toast(...)` (`import marimo as mo`).",
270
+ );
271
+ });
272
+
273
+ it("renders the shared preview template with optional session context", () => {
274
+ expect(
275
+ getRawPrompt({ ...CONNECTION, session: "s_ab12cd" }, null, PAIR_PREVIEW),
276
+ ).toBe(
277
+ "Pair with me on this running marimo notebook.\n\n" +
278
+ "URL: http://localhost:8000\n" +
279
+ "File: notebooks/example.py\n" +
280
+ "Session: s_ab12cd\n\n" +
281
+ "Run `uvx marimo@latest pair --help` first.\n" +
282
+ "Use `uvx marimo@latest` for all marimo commands.\n\n" +
283
+ "Once connected, send a fun toast using `mo.status.toast(...)` (`import marimo as mo`).",
284
+ );
285
+ });
286
+
172
287
  it("references the file-scoped execute-code command", () => {
173
288
  const prompt = getRawPrompt(CONNECTION, null);
174
289
  expect(prompt).toContain(
@@ -0,0 +1,218 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import {
4
+ act,
5
+ fireEvent,
6
+ render,
7
+ screen,
8
+ waitFor,
9
+ within,
10
+ } from "@testing-library/react";
11
+ import { Provider } from "jotai";
12
+ import type React from "react";
13
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
14
+ import { PAIR_PREVIEW } from "@/__tests__/fixtures/pair-preview";
15
+ import { Dialog } from "@/components/ui/dialog";
16
+ import { TooltipProvider } from "@/components/ui/tooltip";
17
+ import { pairPreviewAtom } from "@/core/config/pair";
18
+ import { getSessionId } from "@/core/kernel/session";
19
+ import { filenameAtom } from "@/core/saving/file-state";
20
+ import {
21
+ DEFAULT_RUNTIME_CONFIG,
22
+ runtimeConfigAtom,
23
+ } from "@/core/runtime/config";
24
+ import { store } from "@/core/state/jotai";
25
+ import { PairWithAgentModal } from "../pair-with-agent-modal";
26
+ import { copyToClipboard } from "@/utils/copy";
27
+
28
+ vi.mock("@/utils/copy", () => ({
29
+ copyToClipboard: vi.fn().mockResolvedValue(undefined),
30
+ }));
31
+
32
+ function wrapper({ children }: { children: React.ReactNode }) {
33
+ return (
34
+ <Provider store={store}>
35
+ <TooltipProvider>
36
+ <Dialog open={true}>{children}</Dialog>
37
+ </TooltipProvider>
38
+ </Provider>
39
+ );
40
+ }
41
+
42
+ describe("PairWithAgentModal", () => {
43
+ beforeEach(() => {
44
+ vi.clearAllMocks();
45
+ store.set(pairPreviewAtom, PAIR_PREVIEW);
46
+ store.set(filenameAtom, "/project/my notebook.py");
47
+ store.set(runtimeConfigAtom, {
48
+ ...DEFAULT_RUNTIME_CONFIG,
49
+ url: "http://localhost:8000",
50
+ });
51
+ window.history.replaceState({}, "", "/?file=notebook.py");
52
+ vi.stubGlobal(
53
+ "fetch",
54
+ vi.fn().mockResolvedValue(new Response(JSON.stringify({ token: null }))),
55
+ );
56
+ });
57
+
58
+ afterEach(() => {
59
+ store.set(pairPreviewAtom, undefined);
60
+ store.set(filenameAtom, null);
61
+ vi.unstubAllGlobals();
62
+ });
63
+
64
+ it.each(["Claude", "Codex", "OpenCode", "Prompt"])(
65
+ "omits skill installation and includes the current session in %s preview",
66
+ async (tab) => {
67
+ await act(async () => {
68
+ render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
69
+ });
70
+ fireEvent.mouseDown(screen.getByRole("tab", { name: tab }), {
71
+ button: 0,
72
+ ctrlKey: false,
73
+ });
74
+
75
+ const panel = screen.getByRole("tabpanel");
76
+ expect(within(panel).queryAllByText(/skill/i)).toEqual([]);
77
+ expect(panel).toHaveTextContent(getSessionId());
78
+ expect(panel).toHaveTextContent(
79
+ tab === "Prompt" ? "File: notebook.py" : "--file notebook.py",
80
+ );
81
+ expect(panel).not.toHaveTextContent("/project/my notebook.py");
82
+ expect(panel).toHaveTextContent(
83
+ tab === "Prompt" ? "1. Copy this prompt" : "1. Run in your terminal",
84
+ );
85
+ },
86
+ );
87
+
88
+ it.each(["Claude", "Codex", "OpenCode", "Prompt"])(
89
+ "includes the known notebook file in %s preview when the URL has no file",
90
+ async (tab) => {
91
+ window.history.replaceState({}, "", "/");
92
+ await act(async () => {
93
+ render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
94
+ });
95
+ fireEvent.mouseDown(screen.getByRole("tab", { name: tab }), {
96
+ button: 0,
97
+ ctrlKey: false,
98
+ });
99
+
100
+ const panel = screen.getByRole("tabpanel");
101
+ const fileHint =
102
+ tab === "Prompt"
103
+ ? "File: /project/my notebook.py"
104
+ : "--file '/project/my notebook.py'";
105
+ expect(panel).toHaveTextContent(fileHint);
106
+ fireEvent.click(
107
+ within(panel).getByRole("button", {
108
+ name: tab === "Prompt" ? "Copy prompt" : "Copy command",
109
+ }),
110
+ );
111
+ await waitFor(() => expect(copyToClipboard).toHaveBeenCalledOnce());
112
+ expect(vi.mocked(copyToClipboard).mock.calls[0][0]).toContain(fileHint);
113
+ },
114
+ );
115
+
116
+ it.each([null, ""])(
117
+ "omits the file when the URL and notebook filename are absent (%s)",
118
+ async (filename) => {
119
+ window.history.replaceState({}, "", "/");
120
+ store.set(filenameAtom, filename);
121
+ await act(async () => {
122
+ render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
123
+ });
124
+ expect(screen.getByRole("tabpanel")).not.toHaveTextContent("--file");
125
+ fireEvent.mouseDown(screen.getByRole("tab", { name: "Prompt" }), {
126
+ button: 0,
127
+ ctrlKey: false,
128
+ });
129
+ expect(screen.getByRole("tabpanel")).not.toHaveTextContent("File:");
130
+ },
131
+ );
132
+
133
+ it.each(["Claude", "Codex", "OpenCode", "Prompt"])(
134
+ "preserves the %s skill flow without preview configuration",
135
+ async (tab) => {
136
+ store.set(pairPreviewAtom, undefined);
137
+ window.history.replaceState({}, "", "/");
138
+ await act(async () => {
139
+ render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
140
+ });
141
+ fireEvent.mouseDown(screen.getByRole("tab", { name: tab }), {
142
+ button: 0,
143
+ ctrlKey: false,
144
+ });
145
+
146
+ const panel = screen.getByRole("tabpanel");
147
+ expect(
148
+ within(panel).getByText("npx skills add marimo-team/marimo-pair"),
149
+ ).toBeVisible();
150
+ expect(panel).not.toHaveTextContent(getSessionId());
151
+ expect(panel).not.toHaveTextContent("MARIMO_PAIR_NEXT");
152
+ expect(panel).not.toHaveTextContent("/project/my notebook.py");
153
+ expect(panel).toHaveTextContent(
154
+ tab === "Prompt" ? "2. Copy this prompt" : "2. Run in your terminal",
155
+ );
156
+ },
157
+ );
158
+
159
+ it.each([true, false])(
160
+ "masks the raw token but copies it intact (preview=%s)",
161
+ async (preview) => {
162
+ store.set(pairPreviewAtom, preview ? PAIR_PREVIEW : undefined);
163
+ vi.mocked(fetch).mockResolvedValue(
164
+ new Response(JSON.stringify({ token: "secret-token" })),
165
+ );
166
+ await act(async () => {
167
+ render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
168
+ });
169
+ fireEvent.mouseDown(screen.getByRole("tab", { name: "Prompt" }), {
170
+ button: 0,
171
+ ctrlKey: false,
172
+ });
173
+
174
+ const panel = screen.getByRole("tabpanel");
175
+ expect(panel).toHaveTextContent("********oken");
176
+ expect(panel).not.toHaveTextContent("secret-token");
177
+ fireEvent.click(
178
+ within(panel).getByRole("button", { name: "Copy prompt" }),
179
+ );
180
+ await waitFor(() => expect(copyToClipboard).toHaveBeenCalledOnce());
181
+ expect(vi.mocked(copyToClipboard).mock.calls[0][0]).toContain(
182
+ preview ? "export MARIMO_TOKEN=secret-token" : "--token secret-token",
183
+ );
184
+ expect(vi.mocked(copyToClipboard).mock.calls[0][0]).not.toContain(
185
+ "********oken",
186
+ );
187
+ },
188
+ );
189
+
190
+ it("copies a preview terminal command without the token and lets the user copy the token separately", async () => {
191
+ vi.mocked(fetch).mockResolvedValue(
192
+ new Response(JSON.stringify({ token: "secret-token" })),
193
+ );
194
+ await act(async () => {
195
+ render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
196
+ });
197
+
198
+ const panel = screen.getByRole("tabpanel");
199
+ expect(panel).toHaveTextContent("2. Paste when prompted for a token");
200
+ expect(panel).not.toHaveTextContent("secret-token");
201
+ fireEvent.click(
202
+ within(panel).getByRole("button", { name: "Copy command" }),
203
+ );
204
+ await waitFor(() =>
205
+ expect(copyToClipboard).toHaveBeenCalledWith(
206
+ String.raw`claude "$(MARIMO_PAIR_NEXT=1 uvx marimo@latest pair prompt \
207
+ --url http://localhost:8000/ \
208
+ --file notebook.py \
209
+ --session ${getSessionId()} \
210
+ --with-token)"`,
211
+ ),
212
+ );
213
+ fireEvent.click(within(panel).getByRole("button", { name: "Copy token" }));
214
+ await waitFor(() =>
215
+ expect(copyToClipboard).toHaveBeenLastCalledWith("secret-token"),
216
+ );
217
+ });
218
+ });
@@ -3,6 +3,7 @@
3
3
  import { assertNever } from "@/utils/assertNever";
4
4
  import { KnownQueryParams } from "@/core/constants";
5
5
  import { shellQuote } from "@/utils/shell";
6
+ import type { PairPreviewConfig } from "@/core/config/pair";
6
7
 
7
8
  export type AgentTab = "claude" | "codex" | "opencode" | "prompt";
8
9
 
@@ -37,8 +38,9 @@ function getFileFlag(file: string | undefined): string {
37
38
  /** Identifies the specific running notebook to pair on. */
38
39
  export interface ConnectionInfo {
39
40
  url: string;
40
- /** The server's file key, when the page URL identifies a notebook. */
41
+ /** The server's file key or notebook filename, when known. */
41
42
  file?: string;
43
+ session?: string;
42
44
  }
43
45
 
44
46
  /**
@@ -47,19 +49,29 @@ export interface ConnectionInfo {
47
49
  */
48
50
  export function getTerminalCommand(
49
51
  agent: Exclude<AgentTab, "prompt">,
50
- { url, file }: ConnectionInfo,
52
+ { url, file, session }: ConnectionInfo,
51
53
  withToken: boolean,
54
+ preview?: PairPreviewConfig,
52
55
  ): string {
53
- const fileFlag = getFileFlag(file);
54
- const tokenFlag = withToken ? " --with-token" : "";
55
- const base = `${getMarimoCommand()} pair prompt --url ${shellQuote(url)}${fileFlag}${tokenFlag}`;
56
+ const command = preview?.command ?? getMarimoCommand();
57
+ const prefix = preview ? "MARIMO_PAIR_NEXT=1 " : "";
58
+ const base = [
59
+ `${prefix}${command} pair prompt`,
60
+ `--url ${shellQuote(url)}`,
61
+ file ? `--file ${shellQuote(file)}` : "",
62
+ preview && session ? `--session ${shellQuote(session)}` : "",
63
+ withToken ? "--with-token" : "",
64
+ preview ? "" : `--${agent}`,
65
+ ]
66
+ .filter(Boolean)
67
+ .join(preview ? " \\\n " : " ");
56
68
  switch (agent) {
57
69
  case "claude":
58
- return `claude "$(${base} --claude)"`;
70
+ return `claude "$(${base})"`;
59
71
  case "codex":
60
- return `codex "$(${base} --codex)"`;
72
+ return `codex "$(${base})"`;
61
73
  case "opencode":
62
- return `opencode --prompt "$(${base} --opencode)"`;
74
+ return `opencode --prompt "$(${base})"`;
63
75
  default:
64
76
  assertNever(agent);
65
77
  }
@@ -71,9 +83,23 @@ export function getTerminalCommand(
71
83
  * an agent behaves the same as the terminal commands.
72
84
  */
73
85
  export function getRawPrompt(
74
- { url, file }: ConnectionInfo,
86
+ { url, file, session }: ConnectionInfo,
75
87
  token: string | null,
88
+ preview?: PairPreviewConfig,
76
89
  ): string {
90
+ if (preview) {
91
+ const { command, templates } = preview;
92
+ return formatPrompt(templates.prompt, {
93
+ command,
94
+ url,
95
+ file: file ? formatPrompt(templates.file, { file }) : "",
96
+ session: session ? formatPrompt(templates.session, { session }) : "",
97
+ authentication: token
98
+ ? formatPrompt(templates.token, { token: shellQuote(token) })
99
+ : "",
100
+ });
101
+ }
102
+
77
103
  const fileFlag = getFileFlag(file);
78
104
  const fileHint = file ? ` (file ${file})` : "";
79
105
  const executeCmd = `execute-code.sh --url ${shellQuote(url)}${fileFlag}`;
@@ -91,6 +117,17 @@ export function getRawPrompt(
91
117
  ].join("\n");
92
118
  }
93
119
 
120
+ function formatPrompt(
121
+ template: string,
122
+ values: Record<string, string>,
123
+ ): string {
124
+ // Replace once so inserted file names or tokens cannot become placeholders.
125
+ return template.replaceAll(
126
+ /\{(\w+)\}/g,
127
+ (placeholder, key: string) => values[key] ?? placeholder,
128
+ );
129
+ }
130
+
94
131
  /** Mask all but the last 4 chars of a token for display. */
95
132
  export function maskToken(token: string): string {
96
133
  if (token.length <= 4) {
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { CheckIcon, CopyIcon } from "lucide-react";
4
4
  import React, { useEffect, useState } from "react";
5
+ import { useAtomValue } from "jotai";
5
6
  import { Button } from "@/components/ui/button";
6
7
  import {
7
8
  DialogContent,
@@ -16,6 +17,9 @@ import { Events } from "@/utils/events";
16
17
  import { Tooltip } from "@/components/ui/tooltip";
17
18
  import { asRemoteURL, useRuntimeManager } from "@/core/runtime/config";
18
19
  import { API } from "@/core/network/api";
20
+ import { pairPreviewAtom } from "@/core/config/pair";
21
+ import { getSessionId } from "@/core/kernel/session";
22
+ import { useFilename } from "@/core/saving/filename";
19
23
  import {
20
24
  AGENT_LABELS,
21
25
  AGENT_TABS,
@@ -49,11 +53,17 @@ export const PairWithAgentModal: React.FC<{
49
53
  }> = ({ onClose }) => {
50
54
  const [activeTab, setActiveTab] = useState<AgentTab>("claude");
51
55
  const runtimeManager = useRuntimeManager();
56
+ const preview = useAtomValue(pairPreviewAtom);
57
+ const filename = useFilename();
58
+ const commandStep = preview ? 1 : 2;
52
59
  const authToken = useAuthToken();
53
60
  const hasToken = Boolean(authToken);
54
61
  const connection: ConnectionInfo = {
55
62
  url: runtimeManager.httpURL.toString(),
56
- file: getFileFromURL(window.location.href),
63
+ file:
64
+ getFileFromURL(window.location.href) ??
65
+ (preview ? filename || undefined : undefined),
66
+ session: preview ? getSessionId() : undefined,
57
67
  };
58
68
 
59
69
  return (
@@ -94,23 +104,34 @@ export const PairWithAgentModal: React.FC<{
94
104
  value={tab}
95
105
  className="mt-4 flex flex-col gap-4"
96
106
  >
97
- <Step
98
- index={1}
99
- title="Install the skill"
100
- hint="Run once per machine."
101
- >
102
- <CommandBlock command={SKILL_INSTALL} />
103
- </Step>
104
- <Step index={2} title="Run in your terminal">
107
+ {!preview && (
108
+ <Step
109
+ index={1}
110
+ title="Install the skill"
111
+ hint="Run once per machine."
112
+ >
113
+ <CommandBlock command={SKILL_INSTALL} />
114
+ </Step>
115
+ )}
116
+ <Step index={commandStep} title="Run in your terminal">
105
117
  <CommandBlock
106
- command={getTerminalCommand(tab, connection, hasToken)}
118
+ command={getTerminalCommand(
119
+ tab,
120
+ connection,
121
+ hasToken,
122
+ preview,
123
+ )}
107
124
  />
108
125
  </Step>
109
126
  {hasToken && authToken && (
110
- <Step index={3} title="Paste when prompted for a token">
127
+ <Step
128
+ index={commandStep + 1}
129
+ title="Paste when prompted for a token"
130
+ >
111
131
  <CommandBlock
112
132
  command={authToken}
113
133
  display={maskToken(authToken)}
134
+ copyLabel="Copy token"
114
135
  />
115
136
  </Step>
116
137
  )}
@@ -118,15 +139,17 @@ export const PairWithAgentModal: React.FC<{
118
139
  ))}
119
140
 
120
141
  <TabsContent value="prompt" className="mt-4 flex flex-col gap-4">
142
+ {!preview && (
143
+ <Step
144
+ index={1}
145
+ title="Make sure the marimo-pair skill is available to your agent"
146
+ hint="Skip if your agent already has it."
147
+ >
148
+ <CommandBlock command={SKILL_INSTALL} />
149
+ </Step>
150
+ )}
121
151
  <Step
122
- index={1}
123
- title="Make sure the marimo-pair skill is available to your agent"
124
- hint="Skip if your agent already has it."
125
- >
126
- <CommandBlock command={SKILL_INSTALL} />
127
- </Step>
128
- <Step
129
- index={2}
152
+ index={commandStep}
130
153
  title="Copy this prompt into your agent"
131
154
  hint={
132
155
  hasToken
@@ -135,10 +158,11 @@ export const PairWithAgentModal: React.FC<{
135
158
  }
136
159
  >
137
160
  <CommandBlock
138
- command={getRawPrompt(connection, authToken)}
161
+ command={getRawPrompt(connection, authToken, preview)}
139
162
  display={getRawPrompt(
140
163
  connection,
141
164
  authToken ? maskToken(authToken) : null,
165
+ preview,
142
166
  )}
143
167
  multiline={true}
144
168
  />
@@ -177,7 +201,8 @@ const CommandBlock: React.FC<{
177
201
  command: string;
178
202
  display?: string;
179
203
  multiline?: boolean;
180
- }> = ({ command, display, multiline = false }) => {
204
+ copyLabel?: string;
205
+ }> = ({ command, display, multiline = false, copyLabel = "Copy command" }) => {
181
206
  const [copied, setCopied] = useState(false);
182
207
 
183
208
  const copy = Events.stopPropagation(async (e) => {
@@ -195,6 +220,7 @@ const CommandBlock: React.FC<{
195
220
  </pre>
196
221
  <Tooltip content="Copied!" open={copied}>
197
222
  <Button
223
+ aria-label="Copy prompt"
198
224
  onClick={copy}
199
225
  size="xs"
200
226
  variant="ghost"
@@ -213,11 +239,11 @@ const CommandBlock: React.FC<{
213
239
 
214
240
  return (
215
241
  <div className="flex items-center gap-2 rounded-md bg-muted px-3 py-2 font-mono text-xs">
216
- <code className="flex-1 select-all wrap-break-word">
242
+ <code className="flex-1 whitespace-pre-wrap select-all wrap-break-word">
217
243
  {display ?? command}
218
244
  </code>
219
245
  <Tooltip content="Copied!" open={copied}>
220
- <Button onClick={copy} size="xs" variant="ghost">
246
+ <Button aria-label={copyLabel} onClick={copy} size="xs" variant="ghost">
221
247
  {copied ? (
222
248
  <CheckIcon size={14} strokeWidth={1.5} />
223
249
  ) : (
@@ -124,6 +124,7 @@ export const AddCellWithAI: React.FC<{
124
124
  );
125
125
  const body = {
126
126
  ...completionBody,
127
+ id: options.id,
127
128
  code: "",
128
129
  prompt: "", // Don't need prompt since we are using messages
129
130
  language: language,
@@ -124,7 +124,9 @@ export const AiCompletionEditor: React.FC<Props> = ({
124
124
  new DefaultChatTransport<CompletionUIMessage>({
125
125
  api: runtimeManager.getAiURL("completion").toString(),
126
126
  headers: () => runtimeManager.headers(),
127
- prepareSendMessagesRequest: ({ body }) => ({ body: body ?? {} }),
127
+ prepareSendMessagesRequest: ({ body, id }) => ({
128
+ body: { ...body, id },
129
+ }),
128
130
  }),
129
131
  [runtimeManager],
130
132
  );