@marimo-team/frontend 0.25.1-dev90 → 0.25.1

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 (365) hide show
  1. package/dist/assets/{ConnectedDataExplorerComponent-Bdak5-o8.js → ConnectedDataExplorerComponent-DJ5jwKfp.js} +1 -1
  2. package/dist/assets/{ErrorBoundary-DrliQ_7g.js → ErrorBoundary-BeE1cdgM.js} +2 -2
  3. package/dist/assets/{ImageComparisonComponent-DLxkuRy3.js → ImageComparisonComponent-pCLPT6RA.js} +1 -1
  4. package/dist/assets/LazyAnyLanguageCodeMirror-B_IYTCX5.js +2 -0
  5. package/dist/assets/{RunButton-qknEPbRR.js → RunButton-C2kFUhyO.js} +10 -10
  6. package/dist/assets/{abnfDiagram-VCTEODGH-dm832Yw9.js → abnfDiagram-VCTEODGH-CSPJSGaM.js} +1 -1
  7. package/dist/assets/add-cell-with-ai-BFgdVPaN.js +76 -0
  8. package/dist/assets/{agent-panel-CJva58SL.js → agent-panel-Dh7yWh9P.js} +5 -5
  9. package/dist/assets/ai-model-dropdown-DJ2elMLM.js +5 -0
  10. package/dist/assets/{any-language-editor-B7QvjaYO.js → any-language-editor-Cj_HzlZY.js} +1 -1
  11. package/dist/assets/app-config-button-DBCTefOX.js +1 -0
  12. package/dist/assets/{architectureDiagram-5GKGNRK7-CiRaYU5G.js → architectureDiagram-5GKGNRK7-7iukRoC9.js} +1 -1
  13. package/dist/assets/{blockDiagram-I7D4REHJ-BK3JcsXA.js → blockDiagram-I7D4REHJ-cuhpQB60.js} +1 -1
  14. package/dist/assets/{c4Diagram-7LVT6UL2-BbMz-zwF.js → c4Diagram-7LVT6UL2-CMQEYn6T.js} +1 -1
  15. package/dist/assets/{cache-panel-DxdmvFwH.js → cache-panel-C0oB1kE2.js} +1 -1
  16. package/dist/assets/{cells-BcQ8Tm3j.js → cells-CzugpTQu.js} +33 -33
  17. package/dist/assets/channel-CGdeorQk.js +1 -0
  18. package/dist/assets/{chat-display-TI342pMF.js → chat-display-DevO4F8W.js} +1 -1
  19. package/dist/assets/chat-panel-BGw0bqDk.js +4 -0
  20. package/dist/assets/{chat-ui-B1aeiWdr.js → chat-ui-CZZFGlZi.js} +4 -4
  21. package/dist/assets/{chunk-4HAMMTFA-F3WVmmmo.js → chunk-4HAMMTFA-C6Tl7kEY.js} +1 -1
  22. package/dist/assets/{chunk-75Z2AOVW-DA_lihEi.js → chunk-75Z2AOVW-BzzYECN7.js} +1 -1
  23. package/dist/assets/{chunk-DU6HZSFF-J-califE.js → chunk-DU6HZSFF-BlpgvKzl.js} +2 -2
  24. package/dist/assets/{chunk-F27PBJKO-BzJrz4xx.js → chunk-F27PBJKO-BVIgLGPF.js} +1 -1
  25. package/dist/assets/{chunk-GMAD6QVW-mXoq4V-T.js → chunk-GMAD6QVW-hv6IsAru.js} +1 -1
  26. package/dist/assets/{chunk-GVQU2GXP-B-87l3Xt.js → chunk-GVQU2GXP-Duv4ZYb-.js} +1 -1
  27. package/dist/assets/{chunk-IMKFNOWR-yy9Ec7ZM.js → chunk-IMKFNOWR-BIILwBhp.js} +1 -1
  28. package/dist/assets/{chunk-L3NEJ4N5-DLeZO9l0.js → chunk-L3NEJ4N5-B2hafvRU.js} +1 -1
  29. package/dist/assets/{chunk-OSK3NFVY-PWoOtECb.js → chunk-OSK3NFVY-SNGRdtNA.js} +1 -1
  30. package/dist/assets/{chunk-P2QGCYS3-DsJshZeL.js → chunk-P2QGCYS3-_SeE0QXb.js} +1 -1
  31. package/dist/assets/{chunk-POPQ4Y6H-CCg5TpSp.js → chunk-POPQ4Y6H-BB5NNJFE.js} +1 -1
  32. package/dist/assets/{chunk-PWAF6VOD-CrFPAGFi.js → chunk-PWAF6VOD-BZoBqp8x.js} +1 -1
  33. package/dist/assets/{chunk-SHT3W25Y-lri4OQTX.js → chunk-SHT3W25Y-BtLGdeDB.js} +1 -1
  34. package/dist/assets/{chunk-SVP7TREG-B3ISFgJc.js → chunk-SVP7TREG-B0UfxhUP.js} +1 -1
  35. package/dist/assets/{chunk-TICWLB2K-CJ7ETjY3.js → chunk-TICWLB2K-BP7vel_P.js} +1 -1
  36. package/dist/assets/classDiagram-ZZMXUADV-CoNXKNMZ.js +1 -0
  37. package/dist/assets/classDiagram-v2-VYDZK3BY-CoNXKNMZ.js +1 -0
  38. package/dist/assets/code-visibility-BFNYI7hr.js +1 -0
  39. package/dist/assets/command-palette-DN4JoOLD.js +1 -0
  40. package/dist/assets/{common-cDzShymV.js → common-DUCPhoyP.js} +1 -1
  41. package/dist/assets/{components-B3KlHXnD.js → components-CK7CeYlo.js} +1 -1
  42. package/dist/assets/components-DAmFBASm.js +1 -0
  43. package/dist/assets/config-B4VJJibu.js +1 -0
  44. package/dist/assets/{utils-B3E7otfg.js → config-BxZgoFvi.js} +1 -1
  45. package/dist/assets/config-DpGPGznL.js +1 -0
  46. package/dist/assets/{cynefinDiagram-5FMLGOSQ-1zl1wSTB.js → cynefinDiagram-5FMLGOSQ-BARZRQBb.js} +1 -1
  47. package/dist/assets/{dagre-DM-Ch8wC.js → dagre-DVyyr7Cb.js} +1 -1
  48. package/dist/assets/{dagre-GXQ25YYZ-BqoUeivc.js → dagre-GXQ25YYZ-79s0lOP0.js} +1 -1
  49. package/dist/assets/data-grid-overlay-editor-DPfnIudL.js +1 -0
  50. package/dist/assets/{dependency-graph-panel-C-SFFOPF.js → dependency-graph-panel-BN4CLayU.js} +3 -3
  51. package/dist/assets/{diagram-S7CK7UJ4-CF90sb1m.js → diagram-S7CK7UJ4-BCviGhvd.js} +1 -1
  52. package/dist/assets/{diagram-UQ7AKVKN-DgbZAUCH.js → diagram-UQ7AKVKN-Ds-BT46T.js} +1 -1
  53. package/dist/assets/{diagram-VSXAHHWV-DOQllF_r.js → diagram-VSXAHHWV-CK4GkM-q.js} +1 -1
  54. package/dist/assets/{diagram-VX7I27RA-6dC0a_-i.js → diagram-VX7I27RA-BtL2eh0t.js} +1 -1
  55. package/dist/assets/{diagram-Z3DM3KII-C1OXYmlB.js → diagram-Z3DM3KII-C6SmbLBS.js} +1 -1
  56. package/dist/assets/{dist-0HQ0BMF5.js → dist-CQvJDryh.js} +1 -1
  57. package/dist/assets/{documentation-panel-idK3s0j3.js → documentation-panel-CzRbXV5u.js} +1 -1
  58. package/dist/assets/{ebnfDiagram-PWID7BFC-7gC3cuC7.js → ebnfDiagram-PWID7BFC-D_JU4cMQ.js} +1 -1
  59. package/dist/assets/edit-page-jGNYBqQo.js +174 -0
  60. package/dist/assets/{erDiagram-RLTQ6QDP-BsE87b_i.js → erDiagram-RLTQ6QDP-Z9MJ6X-r.js} +1 -1
  61. package/dist/assets/{error-entries-C-LFkdXb.js → error-entries-CDSsy76p.js} +1 -1
  62. package/dist/assets/{error-panel-YAFgCest.js → error-panel-ABvjFG3p.js} +1 -1
  63. package/dist/assets/{feedback-button-sRQQL2TH.js → feedback-button-CZhLiwkO.js} +2 -2
  64. package/dist/assets/file-explorer-panel-DTiEpmVE.js +1 -0
  65. package/dist/assets/file-name-input-C1C8xrjA.js +3 -0
  66. package/dist/assets/filename-fxG7K_MU.js +1 -0
  67. package/dist/assets/flowDiagram-HODETNUW-ncLciK2K.js +1 -0
  68. package/dist/assets/{gallery-page-CH8TM7bV.js → gallery-page-Moi4ZdJ0.js} +1 -1
  69. package/dist/assets/{ganttDiagram-EL5Y4UJY-d9Qqbnau.js → ganttDiagram-EL5Y4UJY-Cgy11k0G.js} +1 -1
  70. package/dist/assets/{gitGraphDiagram-WWUBYQGX-DBunV_fz.js → gitGraphDiagram-WWUBYQGX-CVH8fay_.js} +1 -1
  71. package/dist/assets/glide-data-editor-CbsCWu-K.js +131 -0
  72. package/dist/assets/{graphlib-DDJ8O6ZH.js → graphlib-3maKwbRO.js} +1 -1
  73. package/dist/assets/has-cJsIKK9t.js +1 -0
  74. package/dist/assets/home-page-UAgVOkCZ.js +4 -0
  75. package/dist/assets/index--sT8xf-8.css +2 -0
  76. package/dist/assets/index-DafnX1Xx.js +62 -0
  77. package/dist/assets/{infoDiagram-27XIBGKW-FE7De1I6.js → infoDiagram-27XIBGKW-Dk8U81nJ.js} +1 -1
  78. package/dist/assets/{ishikawaDiagram-5VMMS53U-CSEGYrJ-.js → ishikawaDiagram-5VMMS53U-CzHQlMUQ.js} +1 -1
  79. package/dist/assets/{journeyDiagram-3NMN7TZE-Cp4mxQrE.js → journeyDiagram-3NMN7TZE-DRc8ACzh.js} +1 -1
  80. package/dist/assets/{kanban-definition-UXKFOSKX-d7iAZsOb.js → kanban-definition-UXKFOSKX-CVyVYiI6.js} +1 -1
  81. package/dist/assets/katex-DkE0ukCy.js +257 -0
  82. package/dist/assets/{logs-panel-xMj01DRN.js → logs-panel-Bz5ysVX4.js} +1 -1
  83. package/dist/assets/{mermaid-CNjXdDrA.js → mermaid-D_jPUVqz.js} +4 -4
  84. package/dist/assets/mhchem-CmzEtQj8.js +1 -0
  85. package/dist/assets/{mindmap-definition-YA3MSWOX-CddM9_uP.js → mindmap-definition-YA3MSWOX-D5t6Aq5Z.js} +1 -1
  86. package/dist/assets/{number-overlay-editor-N57if5NT.js → number-overlay-editor-FAD8sjFU.js} +1 -1
  87. package/dist/assets/{outline-panel-Garp-f5k.js → outline-panel-85oiQ0XX.js} +1 -1
  88. package/dist/assets/packages-panel-ClKIaP5S.js +1 -0
  89. package/dist/assets/{panels-CnbmgzkS.js → panels-B48zp96D.js} +9 -9
  90. package/dist/assets/{pegDiagram-XKGWAZYB-CEns8d-M.js → pegDiagram-XKGWAZYB-BTkTm4ej.js} +1 -1
  91. package/dist/assets/{pieDiagram-E7YTZNPT-wyb0xQ0R.js → pieDiagram-E7YTZNPT-Bq6X88M1.js} +1 -1
  92. package/dist/assets/{process-output-ObDfw1uu.js → process-output-Cg2ta3vY.js} +1 -1
  93. package/dist/assets/{quadrantDiagram-AXDQQJYC-BE-xlTLH.js → quadrantDiagram-AXDQQJYC-D_wrOwLk.js} +1 -1
  94. package/dist/assets/{railroadDiagram-O6MQD6OU-DZZVHmr2.js → railroadDiagram-O6MQD6OU-fv8HXW_g.js} +1 -1
  95. package/dist/assets/{requirementDiagram-BXWQKSXE-C3Bf4dd5.js → requirementDiagram-BXWQKSXE-DDOOMUfC.js} +1 -1
  96. package/dist/assets/{reveal-component-DgrXq3Kv.js → reveal-component-CR43gpTZ.js} +3 -3
  97. package/dist/assets/run-page-NR_KFWk8.js +1 -0
  98. package/dist/assets/{sankeyDiagram-P5KCCOFB-BWLQCyjK.js → sankeyDiagram-P5KCCOFB-CvvUY6tS.js} +1 -1
  99. package/dist/assets/scratchpad-panel-DHc-oVb1.js +1 -0
  100. package/dist/assets/{secrets-panel-4dRIaZfY.js → secrets-panel-C-65ZjnM.js} +1 -1
  101. package/dist/assets/{sequenceDiagram-WJ2MYXX4-BIY0mgX7.js → sequenceDiagram-WJ2MYXX4-DdpBhQaS.js} +1 -1
  102. package/dist/assets/session-DXKMv4Bs.js +1 -0
  103. package/dist/assets/session-panel-DiwQqIbt.js +1 -0
  104. package/dist/assets/{SelectionIndicator-DLJBcP4e.js → shell-BSnvn9t7.js} +1 -1
  105. package/dist/assets/snippets-panel-BLmXrneD.js +1 -0
  106. package/dist/assets/{stateDiagram-D77RDMKH-D_xRHRLf.js → stateDiagram-D77RDMKH-D3p19fac.js} +1 -1
  107. package/dist/assets/stateDiagram-v2-MP3YSRHH-CplsDaDl.js +1 -0
  108. package/dist/assets/{swimlanes-42K2YHIH-CDQzpfz0.js → swimlanes-42K2YHIH-CLxCSvU4.js} +1 -1
  109. package/dist/assets/swimlanesDiagram-VR7AAH4N-DPoIFx8T.js +8 -0
  110. package/dist/assets/{swiper-component-j-Lk25wW.js → swiper-component-rqJChT_P.js} +1 -1
  111. package/dist/assets/{terminal-BToH-U1t.js → terminal-CY4h1UmO.js} +1 -1
  112. package/dist/assets/{timeline-definition-24CTP7MA-D5Jgj1fz.js → timeline-definition-24CTP7MA-Cw2PY--x.js} +1 -1
  113. package/dist/assets/tooltip-PLkI_UfK.js +61 -0
  114. package/dist/assets/{tracing-DtKLmNHj.js → tracing-Q8NAdlrA.js} +1 -1
  115. package/dist/assets/tracing-panel-Ci4kY4dr.js +2 -0
  116. package/dist/assets/useCellActionButton-DpYtVfFe.js +1 -0
  117. package/dist/assets/useTheme-DcnXlv0_.js +1 -0
  118. package/dist/assets/{constants-BDbGiS7O.js → utils-B4K466FK.js} +1 -1
  119. package/dist/assets/{vega-component-DVSvu4vP.js → vega-component-BpoSew4K.js} +1 -1
  120. package/dist/assets/{vega-embed-container-QM1zZmYi.js → vega-embed-container-BOJG8mCA.js} +1 -1
  121. package/dist/assets/{vennDiagram-4TSXK5OY-Cv6jxbpX.js → vennDiagram-4TSXK5OY-D8bN6lVl.js} +1 -1
  122. package/dist/assets/{wardleyDiagram-VM6X3IG4-lwv-YoWo.js → wardleyDiagram-VM6X3IG4-CbZdo0SA.js} +1 -1
  123. package/dist/assets/{write-secret-modal-CBUZw27N.js → write-secret-modal-BiG2tahJ.js} +1 -1
  124. package/dist/assets/{xychartDiagram-S5SC5T6Z-CGM4LlFN.js → xychartDiagram-S5SC5T6Z-DUUQ9tau.js} +1 -1
  125. package/dist/assets/youtube-A9VT7NHb.js +1 -0
  126. package/dist/index.html +14 -13
  127. package/package.json +1 -2
  128. package/src/__mocks__/common.ts +3 -3
  129. package/src/__tests__/CellStatus.test.tsx +1 -1
  130. package/src/__tests__/setup.ts +7 -7
  131. package/src/components/app-config/ai-config.tsx +2 -0
  132. package/src/components/app-config/optional-features.tsx +1 -1
  133. package/src/components/chat/chat-panel.tsx +19 -8
  134. package/src/components/data-table/TableTopBar.tsx +2 -2
  135. package/src/components/data-table/__tests__/export-actions.test.tsx +662 -0
  136. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +286 -0
  137. package/src/components/data-table/charts/charts.tsx +26 -13
  138. package/src/components/data-table/column-summary/chart-spec-model.tsx +4 -4
  139. package/src/components/data-table/data-table.tsx +1 -1
  140. package/src/components/data-table/export-actions.tsx +642 -176
  141. package/src/components/data-table/schemas.ts +25 -3
  142. package/src/components/dependency-graph/elements.ts +2 -2
  143. package/src/components/editor/__tests__/package-alert.test.tsx +140 -0
  144. package/src/components/editor/actions/__tests__/pair-with-agent.test.tsx +107 -0
  145. package/src/components/editor/actions/pair-with-agent.tsx +119 -0
  146. package/src/components/editor/actions/useNotebookActions.tsx +2 -1
  147. package/src/components/editor/ai/add-cell-with-ai.tsx +2 -0
  148. package/src/components/editor/ai/staged-cell-submission.ts +2 -2
  149. package/src/components/editor/ai/transport/chat-transport.tsx +1 -1
  150. package/src/components/editor/chrome/components/contribute-snippet-button.tsx +6 -1
  151. package/src/components/editor/connections/database/as-code.ts +44 -44
  152. package/src/components/editor/connections/storage/as-code.ts +3 -3
  153. package/src/components/editor/file-tree/requesting-tree.tsx +16 -13
  154. package/src/components/editor/navigation/clipboard.ts +2 -2
  155. package/src/components/editor/notebook-cell.tsx +2 -2
  156. package/src/components/editor/output/MarimoTracebackOutput.tsx +1 -1
  157. package/src/components/editor/output/VideoOutput.tsx +1 -1
  158. package/src/components/editor/output/ansi-reduce.ts +21 -17
  159. package/src/components/editor/output/console/ConsoleOutput.tsx +3 -3
  160. package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +59 -0
  161. package/src/components/editor/output/console/text-rendering.tsx +3 -1
  162. package/src/components/editor/package-alert.tsx +20 -7
  163. package/src/components/home/components.tsx +1 -0
  164. package/src/components/markdown/markdown-renderer.css +1 -1
  165. package/src/components/pages/run-page.tsx +1 -0
  166. package/src/components/slides/__tests__/minimap-actions.test.tsx +7 -7
  167. package/src/components/slides/__tests__/minimap.test.ts +12 -12
  168. package/src/components/slides/__tests__/slide-scroll-hint.test.tsx +8 -8
  169. package/src/components/static-html/__tests__/share-modal.test.tsx +154 -0
  170. package/src/components/static-html/__tests__/stage-for-publish.test.ts +111 -0
  171. package/src/components/static-html/share-modal.tsx +114 -144
  172. package/src/components/static-html/stage-for-publish.ts +56 -0
  173. package/src/components/static-html/static-banner.tsx +2 -2
  174. package/src/components/ui/links.tsx +6 -1
  175. package/src/components/ui/typography.tsx +16 -8
  176. package/src/core/ai/context/__tests__/registry.test.ts +31 -28
  177. package/src/core/ai/context/providers/cell-output.ts +8 -8
  178. package/src/core/ai/context/providers/datasource.ts +10 -7
  179. package/src/core/ai/context/providers/error.ts +7 -7
  180. package/src/core/ai/context/providers/file.ts +8 -8
  181. package/src/core/ai/context/providers/tables.ts +7 -7
  182. package/src/core/ai/context/providers/variable.ts +7 -7
  183. package/src/core/ai/context/registry.ts +18 -18
  184. package/src/core/ai/ids/ids.ts +5 -5
  185. package/src/core/ai/model-registry.ts +12 -9
  186. package/src/core/ai/staged-cells.ts +6 -6
  187. package/src/core/ai/tools/base.ts +6 -6
  188. package/src/core/ai/tools/edit-notebook-tool.ts +6 -6
  189. package/src/core/ai/tools/registry.ts +4 -4
  190. package/src/core/ai/tools/run-cells-tool.ts +7 -7
  191. package/src/core/ai/tools/sample-tool.ts +6 -6
  192. package/src/core/cells/__tests__/cells.test.ts +27 -0
  193. package/src/core/cells/cells.ts +2 -1
  194. package/src/core/codemirror/cells/debugger-decorations.ts +13 -7
  195. package/src/core/codemirror/cells/line-timing-decorations.ts +9 -9
  196. package/src/core/codemirror/cells/traceback-decorations.ts +7 -4
  197. package/src/core/codemirror/code-lens/extension.ts +13 -13
  198. package/src/core/codemirror/copilot/__tests__/language-server.test.ts +6 -6
  199. package/src/core/codemirror/copilot/__tests__/transport.test.ts +12 -12
  200. package/src/core/codemirror/copilot/language-server.ts +13 -13
  201. package/src/core/codemirror/copilot/transport.ts +1 -1
  202. package/src/core/codemirror/find-replace/search-highlight.ts +5 -5
  203. package/src/core/codemirror/go-to-definition/underline.ts +3 -3
  204. package/src/core/codemirror/keymaps/vim.ts +3 -3
  205. package/src/core/codemirror/language/languages/markdown.ts +8 -8
  206. package/src/core/codemirror/language/languages/python.ts +7 -7
  207. package/src/core/codemirror/language/languages/sql/completion-builder.ts +5 -5
  208. package/src/core/codemirror/language/languages/sql/completion-store.ts +4 -4
  209. package/src/core/codemirror/language/languages/sql/sql.ts +21 -21
  210. package/src/core/codemirror/lsp/__tests__/transports.test.ts +10 -10
  211. package/src/core/codemirror/lsp/federated-lsp.ts +29 -27
  212. package/src/core/codemirror/lsp/notebook-lsp.ts +21 -21
  213. package/src/core/codemirror/misc/__tests__/dnd.test.ts +10 -10
  214. package/src/core/codemirror/misc/__tests__/paste.test.ts +7 -4
  215. package/src/core/codemirror/reactive-references/extension.ts +3 -3
  216. package/src/core/codemirror/rtc/loro/awareness.ts +12 -12
  217. package/src/core/codemirror/rtc/loro/sync.ts +5 -5
  218. package/src/core/codemirror/vim/cursor-visibility.ts +3 -3
  219. package/src/core/dom/ui-element.ts +5 -5
  220. package/src/core/dom/uiregistry.ts +10 -10
  221. package/src/core/hotkeys/hotkeys.ts +9 -9
  222. package/src/core/islands/__tests__/bridge.test.ts +4 -4
  223. package/src/core/islands/__tests__/test-utils.tsx +4 -4
  224. package/src/core/islands/bridge.ts +85 -82
  225. package/src/core/islands/components/web-components.tsx +7 -7
  226. package/src/core/islands/worker/__tests__/controller.test.ts +1 -1
  227. package/src/core/islands/worker/controller.ts +6 -3
  228. package/src/core/islands/worker-factory.ts +2 -2
  229. package/src/core/kernel/RuntimeState.ts +4 -4
  230. package/src/core/kernel/__tests__/session.test.ts +110 -1
  231. package/src/core/kernel/session.ts +46 -15
  232. package/src/core/lsp/__tests__/transport.test.ts +19 -19
  233. package/src/core/lsp/transport.ts +5 -5
  234. package/src/core/network/CachingRequestRegistry.ts +2 -2
  235. package/src/core/network/DeferredRequestRegistry.ts +4 -4
  236. package/src/core/network/__tests__/export-asset-url.test.ts +45 -0
  237. package/src/core/network/export-asset-url.ts +19 -0
  238. package/src/core/network/requests-network.ts +2 -7
  239. package/src/core/runtime/runtime.ts +25 -20
  240. package/src/core/static/virtual-file-tracker.ts +5 -5
  241. package/src/core/wasm/__tests__/bridge.test.ts +2 -2
  242. package/src/core/wasm/bridge.ts +132 -133
  243. package/src/core/wasm/router.ts +5 -5
  244. package/src/core/wasm/store.ts +4 -4
  245. package/src/core/wasm/worker/bootstrap.ts +8 -5
  246. package/src/core/wasm/worker/message-buffer.ts +3 -3
  247. package/src/core/websocket/transports/__tests__/ws.test.ts +8 -8
  248. package/src/core/websocket/transports/basic.ts +15 -8
  249. package/src/core/websocket/transports/sse.ts +7 -7
  250. package/src/core/websocket/transports/transport.ts +3 -3
  251. package/src/core/websocket/transports/ws.ts +9 -7
  252. package/src/css/globals.css +18 -21
  253. package/src/css/katex.min.css +260 -241
  254. package/src/hooks/__tests__/useOverflowDetection.test.tsx +5 -5
  255. package/src/hooks/__tests__/useResizeObserver.test.tsx +12 -12
  256. package/src/plugins/core/registerReactComponent.tsx +5 -5
  257. package/src/plugins/core/sidebar-element.tsx +2 -2
  258. package/src/plugins/impl/ButtonPlugin.tsx +3 -3
  259. package/src/plugins/impl/CheckboxPlugin.tsx +3 -3
  260. package/src/plugins/impl/CodeEditorPlugin.tsx +3 -3
  261. package/src/plugins/impl/DatePickerPlugin.tsx +3 -3
  262. package/src/plugins/impl/DateRangePlugin.tsx +3 -3
  263. package/src/plugins/impl/DateTimePickerPlugin.tsx +3 -3
  264. package/src/plugins/impl/DictPlugin.tsx +3 -3
  265. package/src/plugins/impl/DropdownPlugin.tsx +3 -3
  266. package/src/plugins/impl/FileUploadPlugin.tsx +3 -3
  267. package/src/plugins/impl/MatrixPlugin.tsx +4 -4
  268. package/src/plugins/impl/MicrophonePlugin.tsx +3 -3
  269. package/src/plugins/impl/MultiselectPlugin.tsx +3 -3
  270. package/src/plugins/impl/NumberPlugin.tsx +3 -3
  271. package/src/plugins/impl/RadioPlugin.tsx +9 -5
  272. package/src/plugins/impl/RangeSliderPlugin.tsx +3 -3
  273. package/src/plugins/impl/RefreshPlugin.tsx +3 -3
  274. package/src/plugins/impl/SliderPlugin.tsx +3 -3
  275. package/src/plugins/impl/SwitchPlugin.tsx +3 -3
  276. package/src/plugins/impl/TabsPlugin.tsx +3 -3
  277. package/src/plugins/impl/TextAreaPlugin.tsx +3 -3
  278. package/src/plugins/impl/TextInputPlugin.tsx +3 -3
  279. package/src/plugins/impl/__tests__/MatrixPlugin.test.tsx +2 -2
  280. package/src/plugins/impl/anywidget/model.ts +10 -10
  281. package/src/plugins/impl/anywidget/registry.ts +12 -9
  282. package/src/plugins/impl/anywidget/runtime.ts +11 -9
  283. package/src/plugins/impl/anywidget/widget-binding.ts +6 -6
  284. package/src/plugins/impl/image-comparison/ImageComparisonComponent.tsx +7 -6
  285. package/src/plugins/impl/matplotlib/MatplotlibPlugin.tsx +3 -3
  286. package/src/plugins/impl/matplotlib/__tests__/matplotlib-renderer.test.ts +2 -2
  287. package/src/plugins/impl/matplotlib/matplotlib-renderer.ts +2 -2
  288. package/src/plugins/impl/mpl-interactive/mpl-websocket-shim.ts +10 -10
  289. package/src/plugins/impl/panel/utils.ts +5 -5
  290. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +3 -3
  291. package/src/plugins/impl/vega/VegaPlugin.tsx +3 -3
  292. package/src/plugins/impl/vega/__tests__/use-vega-container-remeasure.test.ts +7 -7
  293. package/src/plugins/impl/vega/__tests__/utils.test.ts +16 -1
  294. package/src/plugins/impl/vega/__tests__/vega.test.ts +37 -0
  295. package/src/plugins/impl/vega/loader.ts +23 -1
  296. package/src/plugins/impl/vega/utils.ts +5 -4
  297. package/src/plugins/layout/AccordionPlugin.tsx +3 -3
  298. package/src/plugins/layout/CalloutPlugin.tsx +3 -3
  299. package/src/plugins/layout/ImageComparisonPlugin.tsx +5 -3
  300. package/src/plugins/layout/JsonOutputPlugin.tsx +3 -3
  301. package/src/plugins/layout/MimeRenderPlugin.tsx +3 -3
  302. package/src/plugins/layout/NavigationMenuPlugin.tsx +3 -3
  303. package/src/plugins/layout/OutlinePlugin.tsx +3 -3
  304. package/src/plugins/layout/ProgressPlugin.tsx +3 -3
  305. package/src/plugins/layout/RoutesPlugin.tsx +6 -8
  306. package/src/plugins/layout/StatPlugin.tsx +3 -3
  307. package/src/plugins/layout/TexPlugin.tsx +3 -3
  308. package/src/plugins/layout/carousel/CarouselPlugin.tsx +4 -4
  309. package/src/plugins/layout/mermaid/MermaidPlugin.tsx +3 -3
  310. package/src/utils/Deferred.ts +6 -6
  311. package/src/utils/async-capture-tracker.ts +10 -7
  312. package/src/utils/errors.ts +5 -5
  313. package/src/utils/id-tree.tsx +83 -80
  314. package/src/utils/lru.ts +1 -1
  315. package/src/utils/multi-map.ts +14 -12
  316. package/src/utils/paths.ts +6 -6
  317. package/src/utils/pluralize.ts +3 -3
  318. package/src/utils/progress.ts +6 -6
  319. package/src/utils/python-poet/poet.ts +14 -14
  320. package/src/utils/routes.ts +4 -2
  321. package/src/utils/semaphore.ts +6 -6
  322. package/src/utils/storage/storage.ts +6 -6
  323. package/src/utils/storage/typed.ts +16 -12
  324. package/src/utils/time.ts +9 -9
  325. package/src/utils/timed-cache.ts +4 -4
  326. package/src/utils/tracer.ts +9 -6
  327. package/dist/assets/LazyAnyLanguageCodeMirror-EBegnOE0.js +0 -2
  328. package/dist/assets/add-cell-with-ai-Ovbsj95O.js +0 -76
  329. package/dist/assets/ai-model-dropdown-1t7m1iFc.js +0 -3
  330. package/dist/assets/app-config-button-vCxNGhe2.js +0 -1
  331. package/dist/assets/channel-ni7ei-yz.js +0 -1
  332. package/dist/assets/chat-panel-CzdR8RG_.js +0 -4
  333. package/dist/assets/classDiagram-ZZMXUADV-CIEFh40S.js +0 -1
  334. package/dist/assets/classDiagram-v2-VYDZK3BY-CIEFh40S.js +0 -1
  335. package/dist/assets/code-visibility-G0W4ym3Y.js +0 -1
  336. package/dist/assets/command-palette-DhdK67bL.js +0 -1
  337. package/dist/assets/components-CQVZgYvR.js +0 -1
  338. package/dist/assets/config-CMqNngzA.js +0 -1
  339. package/dist/assets/config-CmBpjGXk.js +0 -1
  340. package/dist/assets/data-grid-overlay-editor-DbqMOWZn.js +0 -1
  341. package/dist/assets/edit-page-_D_Nv3dJ.js +0 -176
  342. package/dist/assets/file-explorer-panel-LAa-LxTN.js +0 -1
  343. package/dist/assets/file-name-input-BXq6eIDs.js +0 -3
  344. package/dist/assets/flowDiagram-HODETNUW-B4xRBXB1.js +0 -1
  345. package/dist/assets/glide-data-editor-DPMjaN4R.js +0 -131
  346. package/dist/assets/has-CanCEarv.js +0 -1
  347. package/dist/assets/home-page-vDWLpS4F.js +0 -4
  348. package/dist/assets/index-2TQenOiQ.css +0 -2
  349. package/dist/assets/index-CgQsZfww.js +0 -62
  350. package/dist/assets/katex-mNPPSsir.js +0 -257
  351. package/dist/assets/katex-ukvJUC1e.js +0 -257
  352. package/dist/assets/mhchem-DcHTrqjv.js +0 -1
  353. package/dist/assets/packages-panel-B8HU9FAM.js +0 -1
  354. package/dist/assets/run-page-E0qMru0p.js +0 -1
  355. package/dist/assets/scratchpad-panel-CCFyaegO.js +0 -1
  356. package/dist/assets/session-C8nQMsOE.js +0 -1
  357. package/dist/assets/session-panel-CR3BHLhQ.js +0 -1
  358. package/dist/assets/snippets-panel-Dn22yTr_.js +0 -1
  359. package/dist/assets/stateDiagram-v2-MP3YSRHH-BQvYFx_a.js +0 -1
  360. package/dist/assets/swimlanesDiagram-VR7AAH4N-BAn2JKhB.js +0 -8
  361. package/dist/assets/tooltip-B1vGi-z4.js +0 -61
  362. package/dist/assets/tracing-panel-C5ybTQyJ.js +0 -2
  363. package/dist/assets/useCellActionButton-Bm9OZdcq.js +0 -1
  364. package/dist/assets/useTheme-Ho3YdAx3.js +0 -1
  365. package/dist/assets/youtube-ELpGJEdR.js +0 -1
@@ -51,7 +51,7 @@ export class MarimoIslandElement extends HTMLElement {
51
51
  public static readonly editorTagName = ISLAND_TAG_NAMES.CODE_EDITOR;
52
52
  public static readonly styleNamespace = ISLAND_CSS_CLASSES.NAMESPACE;
53
53
 
54
- constructor() {
54
+ public constructor() {
55
55
  super();
56
56
  this.classList.add(MarimoIslandElement.styleNamespace);
57
57
  }
@@ -59,7 +59,7 @@ export class MarimoIslandElement extends HTMLElement {
59
59
  /**
60
60
  * Gets the app ID from the element's data attribute
61
61
  */
62
- get appId(): string {
62
+ public get appId(): string {
63
63
  const appId = this.getAttribute(ISLAND_DATA_ATTRIBUTES.APP_ID);
64
64
  invariant(appId, "Missing data-app-id attribute");
65
65
  return appId;
@@ -68,7 +68,7 @@ export class MarimoIslandElement extends HTMLElement {
68
68
  /**
69
69
  * Whether this island is reactive (has code sent to Python for execution)
70
70
  */
71
- get isReactive(): boolean {
71
+ public get isReactive(): boolean {
72
72
  return this.getAttribute(ISLAND_DATA_ATTRIBUTES.REACTIVE) === "true";
73
73
  }
74
74
 
@@ -76,7 +76,7 @@ export class MarimoIslandElement extends HTMLElement {
76
76
  * Gets the cell ID by looking up the cell index in the notebook state.
77
77
  * Returns undefined for non-reactive islands (they have no corresponding cell).
78
78
  */
79
- get cellId(): CellId | undefined {
79
+ public get cellId(): CellId | undefined {
80
80
  if (!this.isReactive) {
81
81
  return undefined;
82
82
  }
@@ -95,7 +95,7 @@ export class MarimoIslandElement extends HTMLElement {
95
95
  /**
96
96
  * Gets the code for this island cell
97
97
  */
98
- get code(): string {
98
+ public get code(): string {
99
99
  return extractIslandCodeFromEmbed(this);
100
100
  }
101
101
 
@@ -115,7 +115,7 @@ export class MarimoIslandElement extends HTMLElement {
115
115
  * which can happen inside a React render cycle. Rendering
116
116
  * synchronously from there causes "unmount during render" warnings.
117
117
  */
118
- connectedCallback(): void {
118
+ public connectedCallback(): void {
119
119
  const connectionGeneration = ++this.connectionGeneration;
120
120
  this.addEventListener(
121
121
  ISLAND_SOURCE_CHANGED_EVENT,
@@ -296,7 +296,7 @@ export class MarimoIslandElement extends HTMLElement {
296
296
  /**
297
297
  * Cleanup when element is removed from DOM
298
298
  */
299
- disconnectedCallback(): void {
299
+ public disconnectedCallback(): void {
300
300
  this.removeEventListener(
301
301
  ISLAND_SOURCE_CHANGED_EVENT,
302
302
  this.handleSourceChanged,
@@ -5,7 +5,7 @@ import { describe, expect, it, vi } from "vitest";
5
5
  import { ReadonlyWasmController } from "../controller";
6
6
 
7
7
  class TestController extends ReadonlyWasmController {
8
- setPyodide(pyodide: PyodideInterface) {
8
+ public setPyodide(pyodide: PyodideInterface) {
9
9
  this.pyodide = pyodide;
10
10
  }
11
11
  }
@@ -9,7 +9,7 @@ import type { JsonString } from "@/utils/json/base64";
9
9
  import { Logger } from "../../../utils/Logger";
10
10
 
11
11
  export class ReadonlyWasmController extends DefaultWasmController {
12
- override async bootstrap(opts: {
12
+ public override async bootstrap(opts: {
13
13
  version: string;
14
14
  pyodideVersion: string;
15
15
  }): Promise<PyodideInterface> {
@@ -17,7 +17,10 @@ export class ReadonlyWasmController extends DefaultWasmController {
17
17
  return pyodide;
18
18
  }
19
19
 
20
- override async mountFilesystem(opts: { code: string; filename: string }) {
20
+ public override async mountFilesystem(opts: {
21
+ code: string;
22
+ filename: string;
23
+ }) {
21
24
  const { code, filename } = opts;
22
25
  // Write file
23
26
  try {
@@ -34,7 +37,7 @@ export class ReadonlyWasmController extends DefaultWasmController {
34
37
  return { code, filename };
35
38
  }
36
39
 
37
- override async startSession(opts: {
40
+ public override async startSession(opts: {
38
41
  code: string;
39
42
  filename: string | null;
40
43
  onMessage: (message: JsonString<NotificationPayload>) => void;
@@ -39,7 +39,7 @@ export class DefaultWorkerFactory implements WorkerFactory {
39
39
  private readonly url: string;
40
40
  private readonly name: string;
41
41
 
42
- constructor(config: DefaultWorkerFactoryConfig = {}) {
42
+ public constructor(config: DefaultWorkerFactoryConfig = {}) {
43
43
  this.url = config.workerUrl || this.getDefaultWorkerUrl();
44
44
  this.name = config.workerName || getMarimoVersion();
45
45
  }
@@ -47,7 +47,7 @@ export class DefaultWorkerFactory implements WorkerFactory {
47
47
  /**
48
48
  * Creates a new Pyodide worker
49
49
  */
50
- create(): Worker {
50
+ public create(): Worker {
51
51
  return createModuleWorker(new URL(this.url, import.meta.url), {
52
52
  name: this.name,
53
53
  });
@@ -17,7 +17,7 @@ export class RuntimeState {
17
17
  /**
18
18
  * Shared instance of RuntimeState since this must be a singleton.
19
19
  */
20
- static get INSTANCE(): RuntimeState {
20
+ public static get INSTANCE(): RuntimeState {
21
21
  const KEY = "_marimo_private_RuntimeState";
22
22
  if (!window[KEY]) {
23
23
  window[KEY] = new RuntimeState(UI_ELEMENT_REGISTRY);
@@ -31,7 +31,7 @@ export class RuntimeState {
31
31
  private _sendComponentValues: RunRequests["sendComponentValues"] | undefined;
32
32
  private uiElementRegistry: UIElementRegistry;
33
33
 
34
- constructor(uiElementRegistry: UIElementRegistry) {
34
+ public constructor(uiElementRegistry: UIElementRegistry) {
35
35
  this.uiElementRegistry = uiElementRegistry;
36
36
  }
37
37
 
@@ -45,7 +45,7 @@ export class RuntimeState {
45
45
  /**
46
46
  * Start listening for events from UIElements
47
47
  */
48
- start(sendComponentValues: RunRequests["sendComponentValues"]) {
48
+ public start(sendComponentValues: RunRequests["sendComponentValues"]) {
49
49
  if (this.hasStarted) {
50
50
  Logger.warn("RuntimeState already started");
51
51
  return;
@@ -61,7 +61,7 @@ export class RuntimeState {
61
61
  /**
62
62
  * Stop listening for events from UIElements
63
63
  */
64
- stop() {
64
+ public stop() {
65
65
  if (!this.hasStarted) {
66
66
  Logger.warn("RuntimeState already stopped");
67
67
  return;
@@ -1,10 +1,119 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- import { describe, expect, it } from "vitest";
2
+ import { afterEach, describe, expect, it, vi } from "vitest";
3
+ import type { AppMode } from "@/core/mode";
3
4
  import { generateSessionId, isSessionId } from "../session";
4
5
 
6
+ const URL_SESSION_ID = "s_abc123";
7
+
8
+ /**
9
+ * Load a fresh copy of the session module against the given URL and mode.
10
+ * The module caches its session id, so each case needs its own instance.
11
+ */
12
+ async function loadSession(opts: { mode: AppMode | undefined; url: string }) {
13
+ vi.resetModules();
14
+ window.history.replaceState({}, "", opts.url);
15
+ const { store } = await import("@/core/state/jotai");
16
+ const { initialModeAtom } = await import("@/core/mode");
17
+ store.set(initialModeAtom, opts.mode);
18
+ return await import("../session");
19
+ }
20
+
21
+ function currentSearchParams(): URLSearchParams {
22
+ return new URL(window.location.href).searchParams;
23
+ }
24
+
5
25
  describe("Session", () => {
26
+ afterEach(() => {
27
+ window.history.replaceState({}, "", "/");
28
+ });
29
+
6
30
  it("should create a session", () => {
7
31
  const id = generateSessionId();
8
32
  expect(isSessionId(id)).toBe(true);
9
33
  });
34
+
35
+ it("resumes the session named in the URL in edit mode", async () => {
36
+ const { getSessionId } = await loadSession({
37
+ mode: "edit",
38
+ url: `/?session_id=${URL_SESSION_ID}`,
39
+ });
40
+ expect(getSessionId()).toBe(URL_SESSION_ID);
41
+ expect(currentSearchParams().has("session_id")).toBe(false);
42
+ });
43
+
44
+ it("keeps the URL session id in kiosk mode so a refresh resumes it", async () => {
45
+ const { getSessionId } = await loadSession({
46
+ mode: "edit",
47
+ url: `/?kiosk=true&session_id=${URL_SESSION_ID}`,
48
+ });
49
+ expect(getSessionId()).toBe(URL_SESSION_ID);
50
+ expect(currentSearchParams().get("session_id")).toBe(URL_SESSION_ID);
51
+ expect(currentSearchParams().get("kiosk")).toBe("true");
52
+ });
53
+
54
+ it("keeps the kiosk flag in edit mode when the URL names no session", async () => {
55
+ const { getSessionId } = await loadSession({
56
+ mode: "edit",
57
+ url: "/?kiosk=true",
58
+ });
59
+ expect(isSessionId(getSessionId())).toBe(true);
60
+ expect(currentSearchParams().get("kiosk")).toBe("true");
61
+ });
62
+
63
+ it("starts a new session when the URL session id is malformed", async () => {
64
+ const { getSessionId } = await loadSession({
65
+ mode: "edit",
66
+ url: "/?session_id=not-a-session",
67
+ });
68
+ const id = getSessionId();
69
+ expect(isSessionId(id)).toBe(true);
70
+ expect(id).not.toBe("not-a-session");
71
+ });
72
+
73
+ it("ignores the URL session id in read mode", async () => {
74
+ const { getSessionId } = await loadSession({
75
+ mode: "read",
76
+ url: `/?session_id=${URL_SESSION_ID}`,
77
+ });
78
+ const id = getSessionId();
79
+ expect(isSessionId(id)).toBe(true);
80
+ expect(id).not.toBe(URL_SESSION_ID);
81
+ // The param is still scrubbed so a refresh cannot reintroduce it.
82
+ expect(currentSearchParams().has("session_id")).toBe(false);
83
+ });
84
+
85
+ it("drops both the session id and the kiosk flag in read mode", async () => {
86
+ const { getSessionId } = await loadSession({
87
+ mode: "read",
88
+ url: `/?kiosk=true&session_id=${URL_SESSION_ID}`,
89
+ });
90
+ expect(getSessionId()).not.toBe(URL_SESSION_ID);
91
+ expect(currentSearchParams().has("session_id")).toBe(false);
92
+ // The kiosk flag is forwarded to the websocket URL, and the server
93
+ // refuses kiosk connections to an app, so it must not survive here.
94
+ expect(currentSearchParams().has("kiosk")).toBe(false);
95
+ });
96
+
97
+ it("drops a lone kiosk flag in read mode", async () => {
98
+ const { getSessionId } = await loadSession({
99
+ mode: "read",
100
+ url: "/?kiosk=true",
101
+ });
102
+ expect(isSessionId(getSessionId())).toBe(true);
103
+ expect(currentSearchParams().has("kiosk")).toBe(false);
104
+ });
105
+
106
+ it("ignores the URL session id when the mode is not yet known", async () => {
107
+ const { getSessionId } = await loadSession({
108
+ mode: undefined,
109
+ url: `/?session_id=${URL_SESSION_ID}`,
110
+ });
111
+ expect(getSessionId()).not.toBe(URL_SESSION_ID);
112
+ expect(currentSearchParams().has("session_id")).toBe(false);
113
+ });
114
+
115
+ it("returns the same id on every call", async () => {
116
+ const { getSessionId } = await loadSession({ mode: "read", url: "/" });
117
+ expect(getSessionId()).toBe(getSessionId());
118
+ });
10
119
  });
@@ -4,6 +4,8 @@ import { Logger } from "@/utils/Logger";
4
4
  import type { TypedString } from "@/utils/typed";
5
5
  import { updateQueryParams } from "@/utils/urls";
6
6
  import { KnownQueryParams } from "../constants";
7
+ import { initialModeAtom } from "../mode";
8
+ import { store } from "../state/jotai";
7
9
 
8
10
  export type SessionId = TypedString<"SessionId">;
9
11
 
@@ -20,31 +22,60 @@ export function isSessionId(value: string | null): value is SessionId {
20
22
  return /^s_[\da-z]{6}$/.test(value);
21
23
  }
22
24
 
23
- const sessionId = (() => {
25
+ let sessionId: SessionId | null = null;
26
+
27
+ function resolveSessionId(): SessionId {
28
+ // Only the editor lets the URL steer the connection: kiosk mode and
29
+ // external editors reattach to a running session through `session_id`.
30
+ // An app served with `marimo run` never does. There, the session id is the
31
+ // only thing that routes a browser to its kernel, so a link that picked it
32
+ // would let the link's author attach to the reader's session afterwards,
33
+ // and a stray `kiosk` flag would make the server refuse the connection.
34
+ const urlMaySteerConnection = store.get(initialModeAtom) === "edit";
35
+
24
36
  const url = new URL(window.location.href);
25
37
  const id = url.searchParams.get(
26
38
  KnownQueryParams.sessionId,
27
39
  ) as SessionId | null;
28
- if (isSessionId(id)) {
29
- // Remove the session_id from the URL
30
- updateQueryParams((params) => {
31
- // Keep the session_id if we are in kiosk mode
32
- // this is so we can resume the same session if the user refreshes the page
33
- if (params.has(KnownQueryParams.kiosk)) {
34
- return;
35
- }
40
+ const urlSessionId = isSessionId(id) ? id : null;
41
+
42
+ updateQueryParams((params) => {
43
+ if (!urlMaySteerConnection) {
36
44
  params.delete(KnownQueryParams.sessionId);
45
+ params.delete(KnownQueryParams.kiosk);
46
+ return;
47
+ }
48
+ // Keep the session_id in kiosk mode so that a refresh resumes the same
49
+ // session.
50
+ if (params.has(KnownQueryParams.kiosk)) {
51
+ return;
52
+ }
53
+ params.delete(KnownQueryParams.sessionId);
54
+ });
55
+
56
+ if (urlSessionId === null) {
57
+ Logger.debug("Starting a new session", { sessionId: id });
58
+ return generateSessionId();
59
+ }
60
+ if (!urlMaySteerConnection) {
61
+ Logger.debug("Ignoring session_id from URL outside the editor", {
62
+ sessionId: urlSessionId,
37
63
  });
38
- Logger.debug("Connecting to existing session", { sessionId: id });
39
- return id;
64
+ return generateSessionId();
40
65
  }
41
- Logger.debug("Starting a new session", { sessionId: id });
42
- return generateSessionId();
43
- })();
66
+ Logger.debug("Connecting to existing session", { sessionId: urlSessionId });
67
+ return urlSessionId;
68
+ }
44
69
 
45
70
  /**
46
- * Resume an existing session or start a new one
71
+ * Resume an existing session or start a new one.
72
+ *
73
+ * Resolved on first use rather than at import time, because the URL is only
74
+ * trusted once the app mode is known.
47
75
  */
48
76
  export function getSessionId(): SessionId {
77
+ if (sessionId === null) {
78
+ sessionId = resolveSessionId();
79
+ }
49
80
  return sessionId;
50
81
  }
@@ -10,28 +10,28 @@ vi.mock("@/utils/Logger", () => ({
10
10
  }));
11
11
 
12
12
  class FakeWebSocket {
13
- static readonly CONNECTING = 0;
14
- static readonly OPEN = 1;
15
- static readonly CLOSING = 2;
16
- static readonly CLOSED = 3;
17
- static readonly instances: FakeWebSocket[] = [];
18
-
19
- readonly sent: string[] = [];
20
- readonly url: string;
21
- readonly protocols: string | string[] | undefined;
22
- readyState = FakeWebSocket.CONNECTING;
13
+ public static readonly CONNECTING = 0;
14
+ public static readonly OPEN = 1;
15
+ public static readonly CLOSING = 2;
16
+ public static readonly CLOSED = 3;
17
+ public static readonly instances: FakeWebSocket[] = [];
18
+
19
+ public readonly sent: string[] = [];
20
+ public readonly url: string;
21
+ public readonly protocols: string | string[] | undefined;
22
+ public readyState = FakeWebSocket.CONNECTING;
23
23
  private readonly listeners = new Map<
24
24
  string,
25
25
  Set<(event: { data?: unknown }) => void>
26
26
  >();
27
27
 
28
- constructor(url: string, protocols?: string | string[]) {
28
+ public constructor(url: string, protocols?: string | string[]) {
29
29
  this.url = url;
30
30
  this.protocols = protocols;
31
31
  FakeWebSocket.instances.push(this);
32
32
  }
33
33
 
34
- addEventListener(
34
+ public addEventListener(
35
35
  type: string,
36
36
  listener: (event: { data?: unknown }) => void,
37
37
  ) {
@@ -40,34 +40,34 @@ class FakeWebSocket {
40
40
  this.listeners.set(type, listeners);
41
41
  }
42
42
 
43
- send(frame: string) {
43
+ public send(frame: string) {
44
44
  this.sent.push(frame);
45
45
  }
46
46
 
47
- close() {
47
+ public close() {
48
48
  this.readyState = FakeWebSocket.CLOSED;
49
49
  this.emit("close", {});
50
50
  }
51
51
 
52
- open() {
52
+ public open() {
53
53
  this.readyState = FakeWebSocket.OPEN;
54
54
  this.emit("open", {});
55
55
  }
56
56
 
57
- fail() {
57
+ public fail() {
58
58
  this.emit("error", {});
59
59
  }
60
60
 
61
- disconnect() {
61
+ public disconnect() {
62
62
  this.readyState = FakeWebSocket.CLOSED;
63
63
  this.emit("close", {});
64
64
  }
65
65
 
66
- receive(message: JSONRPCMessage) {
66
+ public receive(message: JSONRPCMessage) {
67
67
  this.emit("message", { data: JSON.stringify(message) });
68
68
  }
69
69
 
70
- messages(): JSONRPCMessage[] {
70
+ public messages(): JSONRPCMessage[] {
71
71
  return this.sent.map((frame) => {
72
72
  const message: unknown = JSON.parse(frame);
73
73
  if (!isJSONRPCMessage(message)) {
@@ -100,7 +100,7 @@ export class ReconnectingWebSocketTransport implements Transport {
100
100
 
101
101
  public onReconnect: (() => Promise<void>) | undefined;
102
102
 
103
- constructor(options: ReconnectingWebSocketTransportOptions) {
103
+ public constructor(options: ReconnectingWebSocketTransportOptions) {
104
104
  this.options = {
105
105
  ...options,
106
106
  retries: options.retries ?? 1,
@@ -109,7 +109,7 @@ export class ReconnectingWebSocketTransport implements Transport {
109
109
  this.onReconnect = options.onReconnect;
110
110
  }
111
111
 
112
- async connect(): Promise<void> {
112
+ public async connect(): Promise<void> {
113
113
  if (this.isClosed) {
114
114
  throw new Error("Transport is closed");
115
115
  }
@@ -136,7 +136,7 @@ export class ReconnectingWebSocketTransport implements Transport {
136
136
  return this.isClosed || generation !== this.connectionGeneration;
137
137
  }
138
138
 
139
- send(message: JSONRPCMessage): void {
139
+ public send(message: JSONRPCMessage): void {
140
140
  if (this.isClosed) {
141
141
  return;
142
142
  }
@@ -162,14 +162,14 @@ export class ReconnectingWebSocketTransport implements Transport {
162
162
  this.reconnect();
163
163
  }
164
164
 
165
- onMessage(handler: (message: JSONRPCMessage) => void): () => void {
165
+ public onMessage(handler: (message: JSONRPCMessage) => void): () => void {
166
166
  this.messageHandlers.add(handler);
167
167
  return () => {
168
168
  this.messageHandlers.delete(handler);
169
169
  };
170
170
  }
171
171
 
172
- close(): void {
172
+ public close(): void {
173
173
  if (this.isClosed) {
174
174
  return;
175
175
  }
@@ -22,7 +22,7 @@ export class CachingRequestRegistry<REQ, RES> {
22
22
  private toKey: ToKey<REQ>;
23
23
  private cache: LRUCache<string, Promise<RES>>;
24
24
 
25
- static jsonStringifySortKeys<T>(): ToKey<T> {
25
+ public static jsonStringifySortKeys<T>(): ToKey<T> {
26
26
  return (o: T) => {
27
27
  if (typeof o !== "object" || o === null) {
28
28
  return String(o);
@@ -31,7 +31,7 @@ export class CachingRequestRegistry<REQ, RES> {
31
31
  };
32
32
  }
33
33
 
34
- constructor(
34
+ public constructor(
35
35
  delegate: DeferredRequestRegistry<REQ, RES>,
36
36
  options: CachingOptions<REQ> = {},
37
37
  ) {
@@ -27,7 +27,7 @@ export class DeferredRequestRegistry<REQ, RES> {
27
27
  resolveExistingRequests?: () => RES;
28
28
  };
29
29
 
30
- constructor(
30
+ public constructor(
31
31
  operation: string,
32
32
  makeRequest: (id: RequestId, req: REQ) => Promise<void>,
33
33
  opts: {
@@ -42,7 +42,7 @@ export class DeferredRequestRegistry<REQ, RES> {
42
42
  this.opts = opts;
43
43
  }
44
44
 
45
- async request(opts: REQ): Promise<RES> {
45
+ public async request(opts: REQ): Promise<RES> {
46
46
  if (this.opts.resolveExistingRequests) {
47
47
  const result = this.opts.resolveExistingRequests();
48
48
  for (const deferred of this.requests.values()) {
@@ -63,7 +63,7 @@ export class DeferredRequestRegistry<REQ, RES> {
63
63
  return deferred.promise;
64
64
  }
65
65
 
66
- resolve(requestId: RequestId, response: RES) {
66
+ public resolve(requestId: RequestId, response: RES) {
67
67
  const entry = this.requests.get(requestId);
68
68
  if (entry === undefined) {
69
69
  return;
@@ -73,7 +73,7 @@ export class DeferredRequestRegistry<REQ, RES> {
73
73
  this.requests.delete(requestId);
74
74
  }
75
75
 
76
- reject(requestId: RequestId, error: Error) {
76
+ public reject(requestId: RequestId, error: Error) {
77
77
  const entry = this.requests.get(requestId);
78
78
  if (entry === undefined) {
79
79
  return;
@@ -0,0 +1,45 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { afterEach, describe, expect, it } from "vitest";
4
+ import { withDevAssetUrl } from "../export-asset-url";
5
+ import type { ExportAsHTMLRequest } from "../types";
6
+
7
+ const REQUEST: ExportAsHTMLRequest = {
8
+ download: false,
9
+ includeCode: true,
10
+ files: [],
11
+ };
12
+
13
+ describe("withDevAssetUrl", () => {
14
+ const originalEnv = process.env.NODE_ENV;
15
+
16
+ afterEach(() => {
17
+ process.env.NODE_ENV = originalEnv;
18
+ });
19
+
20
+ it.each(["development", "test"])(
21
+ "defaults assetUrl to the dev server in %s",
22
+ (env) => {
23
+ process.env.NODE_ENV = env;
24
+ expect(withDevAssetUrl(REQUEST).assetUrl).toBe(window.location.origin);
25
+ },
26
+ );
27
+
28
+ it("leaves a caller-provided assetUrl alone in dev", () => {
29
+ process.env.NODE_ENV = "development";
30
+ const cdn = "https://cdn.example.com/dist";
31
+ expect(withDevAssetUrl({ ...REQUEST, assetUrl: cdn }).assetUrl).toBe(cdn);
32
+ });
33
+
34
+ it("does nothing in production", () => {
35
+ process.env.NODE_ENV = "production";
36
+ expect(withDevAssetUrl(REQUEST)).toBe(REQUEST);
37
+ });
38
+
39
+ it("does not mutate the caller's request", () => {
40
+ process.env.NODE_ENV = "development";
41
+ const request = { ...REQUEST };
42
+ withDevAssetUrl(request);
43
+ expect(request.assetUrl).toBeUndefined();
44
+ });
45
+ });
@@ -0,0 +1,19 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import type { ExportAsHTMLRequest } from "./types";
4
+
5
+ /**
6
+ * In dev/test the CDN has no assets for the local build, so exports default
7
+ * to the dev server. Callers publishing to a remote origin pass their own
8
+ * assetUrl, which is left untouched.
9
+ */
10
+ export function withDevAssetUrl(
11
+ request: ExportAsHTMLRequest,
12
+ ): ExportAsHTMLRequest {
13
+ const isDevOrTest =
14
+ process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test";
15
+ if (request.assetUrl == null && isDevOrTest) {
16
+ return { ...request, assetUrl: window.location.origin };
17
+ }
18
+ return request;
19
+ }
@@ -11,6 +11,7 @@ import {
11
11
  waitForConnectionOpen,
12
12
  waitForConnectionOpenIfNotebook,
13
13
  } from "./connection";
14
+ import { withDevAssetUrl } from "./export-asset-url";
14
15
  import type { EditRequests, RunRequests } from "./types";
15
16
 
16
17
  /**
@@ -430,15 +431,9 @@ export function createNetworkRequests(): EditRequests & RunRequests {
430
431
  .then(handleResponse);
431
432
  },
432
433
  exportAsHTML: async (request) => {
433
- if (
434
- process.env.NODE_ENV === "development" ||
435
- process.env.NODE_ENV === "test"
436
- ) {
437
- request.assetUrl = window.location.origin;
438
- }
439
434
  return getClient()
440
435
  .POST("/api/export/html", {
441
- body: request,
436
+ body: withDevAssetUrl(request),
442
437
  parseAs: "text",
443
438
  params: getParams(),
444
439
  })