@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
@@ -1,6 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  import { describe, expect, it } from "vitest";
3
- import { determineMaxDisplayLength, getCopyValue } from "../JsonOutput";
3
+ import { getCopyValue } from "../json-output/formatting";
4
4
 
5
5
  describe("getCopyValue", () => {
6
6
  it("should handle strings without MIME prefixes", () => {
@@ -178,7 +178,6 @@ describe("getCopyValue", () => {
178
178
  });
179
179
 
180
180
  it("should handle empty set", () => {
181
- // Empty set literal in Python is `set()`, not `{}` (which is a dict).
182
181
  expect(getCopyValue("text/plain+set:[]")).toMatchInlineSnapshot(`"set()"`);
183
182
  });
184
183
 
@@ -212,7 +211,7 @@ describe("getCopyValue", () => {
212
211
  it("should handle tuples", () => {
213
212
  const value = "text/plain+tuple:[1,2,3]";
214
213
  const result = getCopyValue(value);
215
- expect(result).toMatchInlineSnapshot(`"(1,2,3)"`);
214
+ expect(result).toMatchInlineSnapshot(`"(1, 2, 3)"`);
216
215
  });
217
216
 
218
217
  it("should handle tuples in mixed types", () => {
@@ -226,7 +225,7 @@ describe("getCopyValue", () => {
226
225
  `
227
226
  "{
228
227
  "key1": 42,
229
- "key2": (1,2,3),
228
+ "key2": (1, 2, 3),
230
229
  "key3": True
231
230
  }"
232
231
  `,
@@ -240,8 +239,8 @@ describe("getCopyValue", () => {
240
239
  expect(result).toMatchInlineSnapshot(`"18446744073709551616"`);
241
240
 
242
241
  const nestedBigInt = {
243
- key1: bigint, // this will be just a string
244
- key2: `text/plain+bigint:${bigint}`, // this will convert to number
242
+ key1: bigint,
243
+ key2: `text/plain+bigint:${bigint}`,
245
244
  key3: true,
246
245
  };
247
246
  const nestedResult = getCopyValue(nestedBigInt);
@@ -260,7 +259,7 @@ describe("getCopyValue", () => {
260
259
  expect(bigintRawResult).toMatchInlineSnapshot(`"18446744073709551616"`);
261
260
 
262
261
  const nestedBigIntRaw = {
263
- key1: bigintRaw, // raw number
262
+ key1: bigintRaw,
264
263
  key2: `text/plain+bigint:${bigintRaw}`,
265
264
  key3: true,
266
265
  };
@@ -277,64 +276,8 @@ describe("getCopyValue", () => {
277
276
  });
278
277
  });
279
278
 
280
- describe("determineMaxDisplayLength", () => {
281
- const sample2DArray = [
282
- [1, 2, 3],
283
- [4, 5, 6],
284
- [7, 8, 9],
285
- [10, 11, 12],
286
- [13, 14, 15],
287
- [16, 17, 18],
288
- [19, 20, 21],
289
- [22, 23, 24],
290
- [25, 26, 27],
291
- [28, 29, 30],
292
- ];
293
-
294
- it("should return undefined for 1 level arrays", () => {
295
- const value = [1, 2, 3];
296
- const result = determineMaxDisplayLength(value);
297
- expect(result).toBeUndefined();
298
- });
299
-
300
- it("should return undefined for 2 level arrays with less than 20 items", () => {
301
- const value = sample2DArray;
302
- const result = determineMaxDisplayLength(value);
303
- expect(result).toBeUndefined();
304
- });
305
-
306
- it("should return 10 for 2 level arrays with more than 20 items", () => {
307
- const longArray = Array.from({ length: 21 }, (_, i) => i);
308
- const value = [...sample2DArray, longArray];
309
- const result = determineMaxDisplayLength(value);
310
- expect(result).toBe(10);
311
- });
312
-
313
- it("should return 5 for 2 level arrays with more than 50 items", () => {
314
- const longArray = Array.from({ length: 51 }, (_, i) => i);
315
- const value = [...sample2DArray, longArray];
316
- const result = determineMaxDisplayLength(value);
317
- expect(result).toBe(5);
318
- });
319
-
320
- it("should return 5 for 3 level arrays with more than 20 items", () => {
321
- const longArray = Array.from({ length: 21 }, (_, i) => i);
322
- const value = [[...sample2DArray], [...sample2DArray, longArray]];
323
- const result = determineMaxDisplayLength(value);
324
- expect(result).toBe(5);
325
- });
326
- });
327
-
328
279
  describe("getCopyValue with encoded non-string keys", () => {
329
- // Keys are encoded by _key_formatter in
330
- // marimo/_output/formatters/structures.py. Frontend must round-trip them
331
- // to Python literals in the copy output.
332
-
333
280
  it("decodes int keys unquoted", () => {
334
- // JS reorders integer-like string keys to the front of object iteration
335
- // (spec-mandated), so `"2"` appears before `"text/plain+int:2"` here.
336
- // This is pre-existing and unrelated to the encoding — both entries
337
- // survive, which is the regression this guards.
338
281
  const value = { "text/plain+int:2": "no", "2": "oh" };
339
282
  expect(getCopyValue(value)).toMatchInlineSnapshot(`
340
283
  "{
@@ -375,7 +318,6 @@ describe("getCopyValue with encoded non-string keys", () => {
375
318
  });
376
319
 
377
320
  it("emits 1-element tuple keys with a trailing comma (Python syntax)", () => {
378
- // `(1)` is just `1` in Python — a 1-tuple needs `(1,)`.
379
321
  const value = {
380
322
  "text/plain+tuple:[1]": "one",
381
323
  "text/plain+tuple:[]": "empty",
@@ -389,7 +331,6 @@ describe("getCopyValue with encoded non-string keys", () => {
389
331
  });
390
332
 
391
333
  it("emits empty frozenset keys as `frozenset()` not `frozenset({})`", () => {
392
- // `frozenset({})` reads like it's constructing from an empty dict.
393
334
  const value = {
394
335
  "text/plain+frozenset:[]": "empty",
395
336
  "text/plain+frozenset:[1]": "single",
@@ -418,12 +359,6 @@ describe("getCopyValue with encoded non-string keys", () => {
418
359
  });
419
360
 
420
361
  it("parses tuple/frozenset payloads containing bare NaN/Infinity", () => {
421
- // Python's json.dumps emits bare `NaN`/`Infinity` inside the embedded
422
- // tuple/frozenset payload strings (JSON spec violation, but ECMA-262-
423
- // friendly via the fallback in jsonParseWithSpecialChar). The outer
424
- // JSON stays strict because those tokens live inside a JSON string
425
- // key/value. Regression for tuple-key payloads that previously broke
426
- // the frontend's `JSON.parse` and threw.
427
362
  const value = {
428
363
  "text/plain+tuple:[NaN]": "tn",
429
364
  "text/plain+tuple:[Infinity, -Infinity]": "ti",
@@ -439,10 +374,6 @@ describe("getCopyValue with encoded non-string keys", () => {
439
374
  });
440
375
 
441
376
  it("falls back to the raw payload for malformed tuple/frozenset", () => {
442
- // `jsonParseWithSpecialChar` returns `{}` on parse failure rather
443
- // than throwing; without an `Array.isArray` guard, the formatters
444
- // would crash on `.length`/`.map`. Pass the raw payload through so
445
- // a malformed wire form doesn't break the whole render.
446
377
  const value = {
447
378
  "text/plain+tuple:not a json list": "t",
448
379
  k: "text/plain+frozenset:also broken",
@@ -513,3 +444,110 @@ describe("getCopyValue with application/ mimetypes", () => {
513
444
  expect(result).toContain('"number": 42');
514
445
  });
515
446
  });
447
+
448
+ describe("Python copy fidelity", () => {
449
+ it.each([
450
+ "<marimo-replace>True</marimo-replace>",
451
+ "\u0000show_more\u000042|$",
452
+ 'quotes " backslash \\ newline\n',
453
+ ])("preserves literal text %j", (value) => {
454
+ expect(getCopyValue(value)).toBe(JSON.stringify(value));
455
+ });
456
+
457
+ it("preserves keys which used to collide after decoding", () => {
458
+ const data = JSON.parse(
459
+ '{"__proto__":1,"<marimo-replace>2</marimo-replace>":2,"text/plain+int:2":3}',
460
+ );
461
+ expect(getCopyValue(data)).toBe(
462
+ '{\n "__proto__": 1,\n "<marimo-replace>2</marimo-replace>": 2,\n 2: 3\n}',
463
+ );
464
+ });
465
+
466
+ it.each([
467
+ ["text/plain+tuple:[42]", "(42,)"],
468
+ ["text/plain+tuple:[]", "()"],
469
+ [
470
+ 'text/plain+tuple:["quote\\\"","back\\\\slash",true,null]',
471
+ '("quote\\\"", "back\\\\slash", True, None)',
472
+ ],
473
+ [
474
+ 'text/plain+set:[true,null,"text/plain:literal"]',
475
+ '{True, None, "text/plain:literal"}',
476
+ ],
477
+ ['text/plain+tuple:["text/plain+int:2"]', '("text/plain+int:2",)'],
478
+ ["text/plain+float:nan", "float('nan')"],
479
+ ["text/plain+float:inf", "float('inf')"],
480
+ ["text/plain+float:-inf", "-float('inf')"],
481
+ ["text/plain+float:1.0", "1.0"],
482
+ ])("copies %s as valid Python", (data, expected) => {
483
+ expect(getCopyValue(data)).toBe(expected);
484
+ });
485
+
486
+ it("distinguishes shared references from cycles", () => {
487
+ const shared = { x: true };
488
+ expect(getCopyValue([shared, shared])).toBe(
489
+ '[\n {\n "x": True\n },\n {\n "x": True\n }\n]',
490
+ );
491
+ const cyclic: unknown[] = [];
492
+ cyclic.push(cyclic);
493
+ expect(() => getCopyValue(cyclic)).toThrow("Cannot copy circular data");
494
+ });
495
+
496
+ it("copies sparse arrays and non-finite numbers as Python literals", () => {
497
+ const values = [0, Number.NaN, Infinity, -Infinity];
498
+ Reflect.deleteProperty(values, "0");
499
+ expect(getCopyValue(values)).toBe(
500
+ "[\n None,\n float('nan'),\n float('inf'),\n -float('inf')\n]",
501
+ );
502
+ });
503
+ });
504
+
505
+ describe("Python copy negative cases", () => {
506
+ it.each([
507
+ "text/plain+unknown:payload",
508
+ "text/plain+tuple",
509
+ "not-text/plain:payload",
510
+ "https://example.com:a:b",
511
+ ])("does not decode unrecognized leaf %j", (value) => {
512
+ expect(getCopyValue(value)).toBe(JSON.stringify(value));
513
+ expect(getCopyValue({ [value]: "literal" })).toBe(
514
+ `{\n ${JSON.stringify(value)}: "literal"\n}`,
515
+ );
516
+ });
517
+
518
+ it.each(["tuple", "set", "frozenset"])(
519
+ "does not invent a %s from malformed or non-array JSON",
520
+ (kind) => {
521
+ for (const payload of ["[broken", "null", "42", '{"x":1}']) {
522
+ expect(getCopyValue(`text/plain+${kind}:${payload}`)).toBe(payload);
523
+ }
524
+ },
525
+ );
526
+
527
+ it("decodes escaped keys and text exactly once", () => {
528
+ const data = {
529
+ "text/plain+str:text/plain+str:text/plain+int:2":
530
+ "text/plain:text/plain+tuple:[42]",
531
+ };
532
+ expect(getCopyValue(data)).toBe(
533
+ '{\n "text/plain+str:text/plain+int:2": "text/plain+tuple:[42]"\n}',
534
+ );
535
+ });
536
+
537
+ it("does not decode dictionary keys inside raw tuple payloads", () => {
538
+ expect(
539
+ getCopyValue(
540
+ 'text/plain+tuple:[{"text/plain+int:2":"text/plain:literal"}]',
541
+ ),
542
+ ).toBe('({"text/plain+int:2": "text/plain:literal"},)');
543
+ });
544
+
545
+ it("rejects indirect cycles without poisoning later copies", () => {
546
+ const data: Record<string, unknown> = { child: [] };
547
+ data.child = [data];
548
+ expect(() => getCopyValue(data)).toThrow("Cannot copy circular data");
549
+ expect(getCopyValue({ child: [true] })).toBe(
550
+ '{\n "child": [\n True\n ]\n}',
551
+ );
552
+ });
553
+ });
@@ -0,0 +1,252 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import {
4
+ determineMaxDisplayLength,
5
+ estimateExpandedLines,
6
+ paginateJson,
7
+ } from "../json-output/pagination";
8
+
9
+ describe("determineMaxDisplayLength", () => {
10
+ const sample2DArray = [
11
+ [1, 2, 3],
12
+ [4, 5, 6],
13
+ [7, 8, 9],
14
+ [10, 11, 12],
15
+ [13, 14, 15],
16
+ [16, 17, 18],
17
+ [19, 20, 21],
18
+ [22, 23, 24],
19
+ [25, 26, 27],
20
+ [28, 29, 30],
21
+ ];
22
+
23
+ it("should return undefined for 1 level arrays", () => {
24
+ const value = [1, 2, 3];
25
+ const result = determineMaxDisplayLength(value);
26
+ expect(result).toBeUndefined();
27
+ });
28
+
29
+ it("should return undefined for 2 level arrays with less than 20 items", () => {
30
+ const value = sample2DArray;
31
+ const result = determineMaxDisplayLength(value);
32
+ expect(result).toBeUndefined();
33
+ });
34
+
35
+ it("should return 10 for 2 level arrays with more than 20 items", () => {
36
+ const longArray = Array.from({ length: 21 }, (_, i) => i);
37
+ const value = [...sample2DArray, longArray];
38
+ const result = determineMaxDisplayLength(value);
39
+ expect(result).toBe(10);
40
+ });
41
+
42
+ it("should return 5 for 2 level arrays with more than 50 items", () => {
43
+ const longArray = Array.from({ length: 51 }, (_, i) => i);
44
+ const value = [...sample2DArray, longArray];
45
+ const result = determineMaxDisplayLength(value);
46
+ expect(result).toBe(5);
47
+ });
48
+
49
+ it("should return 5 for 3 level arrays with more than 20 items", () => {
50
+ const longArray = Array.from({ length: 21 }, (_, i) => i);
51
+ const value = [[...sample2DArray], [...sample2DArray, longArray]];
52
+ const result = determineMaxDisplayLength(value);
53
+ expect(result).toBe(5);
54
+ });
55
+ });
56
+
57
+ describe("estimateExpandedLines", () => {
58
+ it("scalars count as 1", () => {
59
+ expect(estimateExpandedLines(42, 100)).toBe(1);
60
+ expect(estimateExpandedLines(null, 100)).toBe(1);
61
+ });
62
+
63
+ it("flat containers = child count", () => {
64
+ expect(estimateExpandedLines({ a: 1, b: 2, c: 3 }, 100)).toBe(3);
65
+ expect(estimateExpandedLines([1, 2, 3, 4], 100)).toBe(4);
66
+ });
67
+
68
+ it("small nested children expand recursively", () => {
69
+ expect(estimateExpandedLines({ a: { x: 1, y: 2 }, b: 3 }, 100)).toBe(3);
70
+ });
71
+
72
+ it("large children count as 1 collapsed line", () => {
73
+ const big = Array.from({ length: 20 }, (_, i) => i);
74
+ expect(estimateExpandedLines({ a: big, b: 1 }, 100)).toBe(2);
75
+ });
76
+
77
+ it("bails at cap", () => {
78
+ const big = Array.from({ length: 500 }, (_, i) => i);
79
+ expect(estimateExpandedLines(big, 50)).toBe(50);
80
+ });
81
+
82
+ it("bails at max depth", () => {
83
+ const data = { a: { b: { c: { d: { e: { f: 1 } } } } } };
84
+ expect(estimateExpandedLines(data, 100)).toBe(1);
85
+ });
86
+ });
87
+
88
+ describe("paginateJson", () => {
89
+ const options = { limits: {}, pageSize: 30 };
90
+
91
+ it("preserves full originals and reports the remaining count", () => {
92
+ const rows = Array.from({ length: 100 }, (_, i) => i);
93
+ const data = { rows };
94
+ const tree = paginateJson(data, options);
95
+ const displayRows: unknown[] = Reflect.get(tree.data, "rows");
96
+ expect(displayRows.slice(0, 30)).toEqual(rows.slice(0, 30));
97
+ expect(tree.getPage(displayRows[30])).toEqual({
98
+ path: '["rows"]',
99
+ remaining: 70,
100
+ });
101
+ expect(tree.getOriginal(tree.data)).toBe(data);
102
+ expect(tree.getOriginal(displayRows)).toBe(rows);
103
+ expect(tree.getOriginal("value")).toBe("value");
104
+ });
105
+
106
+ it("paginates arrays and objects at the boundary", () => {
107
+ for (const count of [0, 1, 29, 30, 31, 60]) {
108
+ const values = Array.from({ length: count }, (_, i) => i);
109
+ for (const data of [
110
+ values,
111
+ Object.fromEntries(values.map((i) => [`k${i}`, i])),
112
+ ]) {
113
+ const tree = paginateJson(data, options);
114
+ const displayed = Object.values(tree.data);
115
+ expect(displayed.slice(0, 30)).toEqual(values.slice(0, 30));
116
+ expect(displayed).toHaveLength(
117
+ Math.min(count, 30) + Number(count > 30),
118
+ );
119
+ expect(tree.getPage(displayed.at(-1))).toEqual(
120
+ count > 30 ? { path: "[]", remaining: count - 30 } : undefined,
121
+ );
122
+ }
123
+ }
124
+ });
125
+
126
+ it("keeps dotted, quoted, and empty keys distinct without reserving user strings", () => {
127
+ const values = Array.from({ length: 40 }, (_, i) => i);
128
+ const data = { "a.b": values, a: { b: values }, '"': values, "": values };
129
+ const tree = paginateJson(data, { ...options, limits: { '["a.b"]': 60 } });
130
+ expect(Reflect.get(tree.data, "a.b")).toEqual(values);
131
+ for (const path of [["a", "b"], ['"'], [""]]) {
132
+ const node = path.reduce(
133
+ (node: object, key) => Reflect.get(node, key),
134
+ tree.data,
135
+ );
136
+ expect(tree.getPage(Object.values(node).at(-1))).toEqual({
137
+ path: JSON.stringify(path),
138
+ remaining: 10,
139
+ });
140
+ }
141
+ expect(tree.getPage("\u0000show_more\u000040|$")).toBeUndefined();
142
+ expect(tree.getPage(Symbol("show more"))).toBeUndefined();
143
+ });
144
+
145
+ it("preserves __proto__ and empty keys when adding an object page", () => {
146
+ const data = { "": 1, ["__proto__"]: 2, other: 3 };
147
+ const tree = paginateJson(data, { limits: {}, pageSize: 2 });
148
+ expect(Object.values(tree.data).slice(0, 2)).toEqual([1, 2]);
149
+ expect(tree.getOriginal(tree.data)).toBe(data);
150
+ expect(tree.getPage(Object.values(tree.data).at(-1))).toEqual({
151
+ path: "[]",
152
+ remaining: 1,
153
+ });
154
+ });
155
+
156
+ it("does not traverse hidden branches, and caches visible children", () => {
157
+ const read = vi.fn(() => [1, 2]);
158
+ const hidden = vi.fn(() => [3, 4]);
159
+ const data = {
160
+ get visible() {
161
+ return read();
162
+ },
163
+ get hidden() {
164
+ return hidden();
165
+ },
166
+ };
167
+ const tree = paginateJson(data, { limits: {}, pageSize: 1 });
168
+ expect(read).not.toHaveBeenCalled();
169
+ const first = Reflect.get(tree.data, "visible");
170
+ expect(Reflect.get(tree.data, "visible")).toBe(first);
171
+ expect(read).toHaveBeenCalledOnce();
172
+ expect(hidden).not.toHaveBeenCalled();
173
+ });
174
+
175
+ it("paginates deep branches without a depth cutoff", () => {
176
+ const data = {
177
+ deep: {
178
+ more: {
179
+ levels: {
180
+ still: {
181
+ going: {
182
+ deeper: { rows: Array.from({ length: 100 }, (_, i) => i) },
183
+ },
184
+ },
185
+ },
186
+ },
187
+ },
188
+ };
189
+ const tree = paginateJson(data, options);
190
+ const keys = ["deep", "more", "levels", "still", "going", "deeper", "rows"];
191
+ const rows = keys.reduce(
192
+ (node: object, key) => Reflect.get(node, key),
193
+ tree.data,
194
+ );
195
+ expect(Object.values(rows)).toHaveLength(31);
196
+ expect(tree.getPage(Object.values(rows).at(-1))).toEqual({
197
+ path: JSON.stringify(keys),
198
+ remaining: 70,
199
+ });
200
+ });
201
+
202
+ it("preserves non-JSON objects and does not mutate input", () => {
203
+ const date = new Date("2026-01-01");
204
+ const data = Object.freeze({ date, values: Object.freeze([1, 2, 3]) });
205
+ const tree = paginateJson(data, { limits: {}, pageSize: 2 });
206
+ expect(Reflect.get(tree.data, "date")).toBe(date);
207
+ expect(Object.values(Reflect.get(tree.data, "values"))).toHaveLength(3);
208
+ expect(data.values).toEqual([1, 2, 3]);
209
+ });
210
+ });
211
+
212
+ describe("pagination negative cases", () => {
213
+ it("does not overwrite empty or NUL keys with pagination controls", () => {
214
+ const data = { "": "empty", "\0": "nul", "\0\0": "two", last: "last" };
215
+ const tree = paginateJson(data, { limits: {}, pageSize: 3 });
216
+ expect(Object.values(tree.data).slice(0, 3)).toEqual([
217
+ "empty",
218
+ "nul",
219
+ "two",
220
+ ]);
221
+ expect(tree.getPage(Object.values(tree.data).at(-1))).toEqual({
222
+ path: "[]",
223
+ remaining: 1,
224
+ });
225
+ expect(Object.keys(data)).toEqual(["", "\0", "\0\0", "last"]);
226
+ });
227
+
228
+ it("does not accept page markers from another tree", () => {
229
+ const first = paginateJson([1, 2], { limits: {}, pageSize: 1 });
230
+ const second = paginateJson([3, 4], { limits: {}, pageSize: 1 });
231
+ expect(second.getPage(Object.values(first.data).at(-1))).toBeUndefined();
232
+ });
233
+
234
+ it("does not read values beyond the visible page", () => {
235
+ const data = {
236
+ visible: 1,
237
+ get hidden() {
238
+ throw new Error("hidden value read");
239
+ },
240
+ };
241
+ const tree = paginateJson(data, { limits: {}, pageSize: 1 });
242
+ expect(Object.values(tree.data)[0]).toBe(1);
243
+ expect(tree.getOriginal(tree.data)).toBe(data);
244
+ });
245
+
246
+ it("does not expose inherited or non-enumerable fields", () => {
247
+ const data = Object.defineProperty({ visible: 1 }, "hidden", { value: 2 });
248
+ const tree = paginateJson(data, { limits: {}, pageSize: 30 });
249
+ expect(Object.entries(tree.data)).toEqual([["visible", 1]]);
250
+ expect(Reflect.get(tree.data, "toString")).toBeUndefined();
251
+ });
252
+ });
@@ -0,0 +1,167 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import { jsonParseWithSpecialChar } from "@/utils/json/json-parser";
3
+
4
+ export function splitLeaf(leaf: string): [string, string | undefined] {
5
+ const index = leaf.indexOf(":");
6
+ return index < 0
7
+ ? [leaf, undefined]
8
+ : [leaf.slice(index + 1), leaf.slice(0, index)];
9
+ }
10
+
11
+ function formatFloat(value: string): string {
12
+ switch (value.toLowerCase()) {
13
+ case "nan":
14
+ return "float('nan')";
15
+ case "inf":
16
+ case "infinity":
17
+ return "float('inf')";
18
+ case "-inf":
19
+ case "-infinity":
20
+ return "-float('inf')";
21
+ default:
22
+ return value;
23
+ }
24
+ }
25
+
26
+ function formatCollection(
27
+ payload: string,
28
+ kind: "tuple" | "set" | "frozenset",
29
+ ): string {
30
+ const items = jsonParseWithSpecialChar<unknown>(payload);
31
+ // Older servers sent Python literals instead of JSON lists.
32
+ if (!Array.isArray(items)) {
33
+ return payload;
34
+ }
35
+ const inner = items.map((item) => formatPython(item, true)).join(", ");
36
+ switch (kind) {
37
+ case "tuple":
38
+ return `(${inner}${items.length === 1 ? "," : ""})`;
39
+ case "set":
40
+ return items.length ? `{${inner}}` : "set()";
41
+ case "frozenset":
42
+ return items.length ? `frozenset({${inner}})` : "frozenset()";
43
+ }
44
+ }
45
+
46
+ export const formatTuplePayload = (payload: string) =>
47
+ formatCollection(payload, "tuple");
48
+ export const formatSetPayload = (payload: string) =>
49
+ formatCollection(payload, "set");
50
+ export const formatFrozensetPayload = (payload: string) =>
51
+ formatCollection(payload, "frozenset");
52
+
53
+ // Matches the key encoding in marimo/_output/formatters/structures.py.
54
+ export function decodePythonKey(
55
+ key: unknown,
56
+ ): { text: string; quoted: boolean } | undefined {
57
+ if (typeof key !== "string") {
58
+ return undefined;
59
+ }
60
+ const [payload, mime] = splitLeaf(key);
61
+ switch (mime) {
62
+ case "text/plain+str":
63
+ return { text: payload, quoted: true };
64
+ case "text/plain+int":
65
+ case "text/plain+float":
66
+ return { text: payload, quoted: false };
67
+ case "text/plain+bool":
68
+ return { text: payload === "True" ? "True" : "False", quoted: false };
69
+ case "text/plain+none":
70
+ return { text: "None", quoted: false };
71
+ case "text/plain+tuple":
72
+ return { text: formatTuplePayload(payload), quoted: false };
73
+ case "text/plain+frozenset":
74
+ return { text: formatFrozensetPayload(payload), quoted: false };
75
+ default:
76
+ return undefined;
77
+ }
78
+ }
79
+
80
+ function formatKey(key: string): string {
81
+ const decoded = decodePythonKey(key);
82
+ if (!decoded) {
83
+ return JSON.stringify(key);
84
+ }
85
+ if (decoded.quoted) {
86
+ return JSON.stringify(decoded.text);
87
+ }
88
+ return key.startsWith("text/plain+float:")
89
+ ? formatFloat(decoded.text)
90
+ : decoded.text;
91
+ }
92
+
93
+ function formatString(value: string): string {
94
+ const [payload, mime] = splitLeaf(value);
95
+ switch (mime) {
96
+ case "text/plain+float":
97
+ return formatFloat(payload);
98
+ case "text/plain+bigint":
99
+ return BigInt(payload).toString();
100
+ case "text/plain+tuple":
101
+ return formatTuplePayload(payload);
102
+ case "text/plain+set":
103
+ return formatSetPayload(payload);
104
+ case "text/plain+frozenset":
105
+ return formatFrozensetPayload(payload);
106
+ case "text/plain":
107
+ case "text/html":
108
+ case "text/markdown":
109
+ return JSON.stringify(payload);
110
+ default:
111
+ return JSON.stringify(
112
+ mime && /^(image|video|application)\//.test(mime) ? payload : value,
113
+ );
114
+ }
115
+ }
116
+
117
+ function formatPython(value: unknown, compact = false): string {
118
+ const ancestors = new WeakSet<object>();
119
+ function format(node: unknown, depth: number): string {
120
+ if (node == null) {
121
+ return "None";
122
+ }
123
+ switch (typeof node) {
124
+ case "string":
125
+ return compact ? JSON.stringify(node) : formatString(node);
126
+ case "boolean":
127
+ return node ? "True" : "False";
128
+ case "number":
129
+ return formatFloat(String(node));
130
+ case "bigint":
131
+ return node.toString();
132
+ case "object":
133
+ if (ancestors.has(node)) {
134
+ throw new TypeError("Cannot copy circular data");
135
+ }
136
+ ancestors.add(node);
137
+ try {
138
+ const array = Array.isArray(node);
139
+ const entries = array
140
+ ? Array.from(node, (item) => format(item, depth + 1))
141
+ : Object.entries(node).map(
142
+ ([key, item]) =>
143
+ `${compact ? JSON.stringify(key) : formatKey(key)}: ${format(item, depth + 1)}`,
144
+ );
145
+ const [open, close] = array ? ["[", "]"] : ["{", "}"];
146
+ if (entries.length === 0) {
147
+ return open + close;
148
+ }
149
+ if (compact) {
150
+ return `${open}${entries.join(", ")}${close}`;
151
+ }
152
+ const indent = " ".repeat(depth);
153
+ return `${open}\n${indent} ${entries.join(`,\n${indent} `)}\n${indent}${close}`;
154
+ } finally {
155
+ ancestors.delete(node);
156
+ }
157
+
158
+ default:
159
+ return "None";
160
+ }
161
+ }
162
+ return format(value, 0);
163
+ }
164
+
165
+ export function getCopyValue(value: unknown): string {
166
+ return formatPython(value);
167
+ }