@marimo-team/islands 0.25.3-dev6 → 0.25.3-dev60

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 (236) hide show
  1. package/dist/{ConnectedDataExplorerComponent-CNMliVzw.js → ConnectedDataExplorerComponent-Da5OoEUL.js} +14 -14
  2. package/dist/{ErrorBoundary-Ca-Sa492.js → ErrorBoundary-BmHVnL-A.js} +8 -8
  3. package/dist/{abnfDiagram-VCTEODGH-D-Ez91gV.js → abnfDiagram-VCTEODGH-Bz3mrprX.js} +2 -2
  4. package/dist/any-language-editor-k2B8YI3Y.js +75 -0
  5. package/dist/{architectureDiagram-5GKGNRK7-CTpcNtO8.js → architectureDiagram-5GKGNRK7-Cs5ujb9K.js} +1 -1
  6. package/dist/{arrays-CN1djC4k.js → arrays-R6JsImiT.js} +8 -0
  7. package/dist/{button-CvjkwdSk.js → button-BxB3RDjN.js} +1 -1
  8. package/dist/{capabilities-CQ5IjhzL.js → capabilities-BgR0osfN.js} +2 -2
  9. package/dist/{chat-ui-0lKdOoQ3.js → chat-ui-C0_lCzql.js} +1139 -1140
  10. package/dist/{check-EDwSwP89.js → check-P35dc2dH.js} +1 -1
  11. package/dist/{chunk-IMKFNOWR-DGxi-zeo.js → chunk-IMKFNOWR-CcTSaxZt.js} +1 -1
  12. package/dist/{chunk-SHT3W25Y-CV9J3rAB.js → chunk-SHT3W25Y-DcJcjOaO.js} +1 -1
  13. package/dist/{chunk-SVP7TREG-BL-9Fqgg.js → chunk-SVP7TREG-BVtDTjaF.js} +1 -1
  14. package/dist/{chunk-TICWLB2K-Cu_XMjDM.js → chunk-TICWLB2K-BkDFkh8q.js} +1 -1
  15. package/dist/{chunk-YOKDWASO-DPZk8vhS.js → chunk-YOKDWASO-DKplTrBY.js} +20 -20
  16. package/dist/{classDiagram-ZZMXUADV-BXXV13Hq.js → classDiagram-ZZMXUADV-CfAt7HT5.js} +2 -2
  17. package/dist/{classDiagram-v2-VYDZK3BY-B7FXJ1Cp.js → classDiagram-v2-VYDZK3BY-CGEjNYa2.js} +2 -2
  18. package/dist/{code-visibility-NGRjhbtA.js → code-visibility-BWERlt58.js} +6 -6
  19. package/dist/{common-B7_ZSg0P.js → common-E2zdIyjs.js} +4960 -4835
  20. package/dist/{copy-icon-Ft7t9bqz.js → copy-icon-CPoxUG7G.js} +3 -3
  21. package/dist/{cynefinDiagram-5FMLGOSQ-CH96rwba.js → cynefinDiagram-5FMLGOSQ-BW6NmfJi.js} +1 -1
  22. package/dist/{dagre-GXQ25YYZ-BD3PmpFY.js → dagre-GXQ25YYZ-CWg3CTuF.js} +1 -1
  23. package/dist/{data-grid-overlay-editor-B_cmrkBm.js → data-grid-overlay-editor-CHUwPg7P.js} +5 -5
  24. package/dist/{data-grid-types-BH_r7g7l.js → data-grid-types-0PJ-9N5n.js} +1 -1
  25. package/dist/{diagram-S7CK7UJ4-BsSJJldB.js → diagram-S7CK7UJ4-BPxtoS-z.js} +1 -1
  26. package/dist/{diagram-UQ7AKVKN-muQtNZc4.js → diagram-UQ7AKVKN-DA-vJdFL.js} +1 -1
  27. package/dist/{diagram-VX7I27RA-CdhuZAqR.js → diagram-VX7I27RA-D6bkR84S.js} +1 -1
  28. package/dist/{diagram-Z3DM3KII-BD8mXjOj.js → diagram-Z3DM3KII-m60g4cAT.js} +1 -1
  29. package/dist/{dialog-CEBFZNyc.js → dialog-C1PcpCqX.js} +3 -3
  30. package/dist/{dist-DpBpA_n1.js → dist-Lm-D-Vr6.js} +3 -3
  31. package/dist/{dropdown-menu-CP_qCYHC.js → dropdown-menu-B3eUiVJB.js} +177 -169
  32. package/dist/{ebnfDiagram-PWID7BFC-BIN1sweE.js → ebnfDiagram-PWID7BFC-CK99sho2.js} +2 -2
  33. package/dist/{erDiagram-RLTQ6QDP-Ctp5cfoN.js → erDiagram-RLTQ6QDP-Bryq0tdE.js} +1 -1
  34. package/dist/{error-banner-D-WCIiMt.js → error-banner-Sl2FlSJ4.js} +2 -2
  35. package/dist/{error-entries-DSvFWT_M.js → error-entries-CMNtA8NK.js} +959 -959
  36. package/dist/{feedback-button-CTF2Nj9s.js → feedback-button-DwOUm3jt.js} +29 -29
  37. package/dist/{flowDiagram-HODETNUW-3EfLrlFb.js → flowDiagram-HODETNUW-D-3aRpfS.js} +2 -2
  38. package/dist/{formats-CeOmU1MU.js → formats-6pLWWrY2.js} +3 -3
  39. package/dist/{ganttDiagram-EL5Y4UJY-BQT16JSc.js → ganttDiagram-EL5Y4UJY-Dh_4cBJZ.js} +2 -2
  40. package/dist/{glide-data-editor-BQHF9puC.js → glide-data-editor-C9WGlA66.js} +370 -368
  41. package/dist/{glide-utils-DJyqh0EM.js → glide-utils-Bt8_pBa2.js} +16 -16
  42. package/dist/{globals-Cj8iqMEp.js → globals-BwFL--QV.js} +3 -3
  43. package/dist/{highlighted-body-KPVGNVTW-zwjq21tf.js → highlighted-body-KPVGNVTW-3Ziwfz-V.js} +2 -2
  44. package/dist/{html-to-image-B8s5SgGm.js → html-to-image-6xcoTKhC.js} +29 -29
  45. package/dist/{infoDiagram-27XIBGKW-DipQoHxl.js → infoDiagram-27XIBGKW-s1C1kgfU.js} +1 -1
  46. package/dist/{ishikawaDiagram-5VMMS53U-DgFMjSGz.js → ishikawaDiagram-5VMMS53U-CCsRgolY.js} +1 -1
  47. package/dist/{kanban-definition-UXKFOSKX-w1ACVjgl.js → kanban-definition-UXKFOSKX-BrmxDlcc.js} +1 -1
  48. package/dist/{label-BDKF3Afc.js → label-BRnUC0NX.js} +2 -2
  49. package/dist/{linear-Cnaq3Tfb.js → linear-DWERh7eI.js} +121 -73
  50. package/dist/{loader-BQOfjTIJ.js → loader-Bm8cy3e8.js} +12 -12
  51. package/dist/main.js +3177 -2980
  52. package/dist/{mermaid-CHo49Yv-.js → mermaid-DKI0869I.js} +38 -38
  53. package/dist/mermaid-HWGCJPDP-Du3-HPQA.js +2 -0
  54. package/dist/{mindmap-definition-YA3MSWOX-CrT0KSfX.js → mindmap-definition-YA3MSWOX-641xJysF.js} +1 -1
  55. package/dist/{number-overlay-editor-DBcnm0SY.js → number-overlay-editor-Ci9h4-h1.js} +3 -3
  56. package/dist/{objects-CzUgoMIF.js → objects-DCmMvZtn.js} +1 -3
  57. package/dist/{once-BtE-wKRL.js → once-eG8Vmvjd.js} +1 -1
  58. package/dist/{pegDiagram-XKGWAZYB-BqvywNJA.js → pegDiagram-XKGWAZYB-keVUAUeS.js} +2 -2
  59. package/dist/{pieDiagram-E7YTZNPT-BfxJ_XCc.js → pieDiagram-E7YTZNPT-DHktXPO_.js} +1 -1
  60. package/dist/precisionRound-DhAzeRcm.js +49 -0
  61. package/dist/{process-output-CKRXyszb.js → process-output-BJRJFobf.js} +4 -4
  62. package/dist/{quadrantDiagram-AXDQQJYC-Ccw0xMjN.js → quadrantDiagram-AXDQQJYC-Blrm7V7_.js} +1 -1
  63. package/dist/{railroadDiagram-O6MQD6OU-8vFLVTPu.js → railroadDiagram-O6MQD6OU-BVEF4OV_.js} +2 -2
  64. package/dist/{react-vega-BED0DjiB.js → react-vega-C4T9Rb_w.js} +12284 -12270
  65. package/dist/{requirementDiagram-BXWQKSXE-a_MxGvxe.js → requirementDiagram-BXWQKSXE-CWo-UAKV.js} +1 -1
  66. package/dist/{reveal-component-DTBSuGkC.js → reveal-component-Ay-npdvI.js} +19 -19
  67. package/dist/{select-BButFi-d.js → select-B0LRDH0g.js} +4 -4
  68. package/dist/{sequenceDiagram-WJ2MYXX4-CXFMnxGh.js → sequenceDiagram-WJ2MYXX4-CHQvLnq4.js} +1 -1
  69. package/dist/{spec-BwarBpUJ.js → spec-C-LRKGtT.js} +4 -4
  70. package/dist/{stateDiagram-D77RDMKH-DQeA_CwI.js → stateDiagram-D77RDMKH-BOJZgbUJ.js} +2 -2
  71. package/dist/{stateDiagram-v2-MP3YSRHH-BxxQucm1.js → stateDiagram-v2-MP3YSRHH-C53XXdYH.js} +2 -2
  72. package/dist/style.css +1 -1
  73. package/dist/{swimlanes-42K2YHIH-Do6L-cYg.js → swimlanes-42K2YHIH-D2o1gOtw.js} +1 -1
  74. package/dist/{swimlanesDiagram-VR7AAH4N-DUzPvSVU.js → swimlanesDiagram-VR7AAH4N-BHFzN1Wk.js} +2 -2
  75. package/dist/{swiper-component-DKntyn8p.js → swiper-component-DfhRvAFi.js} +2 -2
  76. package/dist/{time-BieKwdYS.js → time-BaMbJs4z.js} +18 -18
  77. package/dist/{timeline-definition-24CTP7MA-D3c46WM5.js → timeline-definition-24CTP7MA-rv7QUa46.js} +1 -1
  78. package/dist/{toDate-vIXljnsr.js → toDate-CjPuEPou.js} +21 -20
  79. package/dist/{toast-B9UMEi8u.js → toast-CFWpihJU.js} +4 -4
  80. package/dist/{tooltip-BO1REbqs.js → tooltip-CKO1pHJo.js} +2 -2
  81. package/dist/{use-restore-focus-BEM7sFWl.js → use-restore-focus-DBCITLeF.js} +1 -1
  82. package/dist/{useAsyncData-Deh5kszT.js → useAsyncData-ChgNtdmE.js} +15 -14
  83. package/dist/{useDateFormatter-D8XruNGG.js → useDateFormatter-c_yJtkAe.js} +2 -2
  84. package/dist/{useDeepCompareMemoize-DXwzRINI.js → useDeepCompareMemoize-Bet7kSsI.js} +1 -1
  85. package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities-BkdeVdXC.js} +1 -1
  86. package/dist/{useLifecycle-j7D9uD1o.js → useLifecycle-VVmz_24o.js} +3 -3
  87. package/dist/{useTheme-CRnjkku0.js → useTheme-DympCsQg.js} +5 -5
  88. package/dist/{vega-component-DTG6zxaY.js → vega-component-DnfQeoyt.js} +147 -147
  89. package/dist/{vega-embed-container-BNHE18G9.js → vega-embed-container--LYZqguO.js} +2 -2
  90. package/dist/{vega-loader.browser-DNST6uVf.js → vega-loader.browser-B9Pz0EGP.js} +298 -507
  91. package/dist/{vennDiagram-4TSXK5OY-BSZD8JcZ.js → vennDiagram-4TSXK5OY-D36Zk7t-.js} +1 -1
  92. package/dist/{wardleyDiagram-VM6X3IG4-48cUWkfA.js → wardleyDiagram-VM6X3IG4-BLBolvyQ.js} +1 -1
  93. package/dist/{xychartDiagram-S5SC5T6Z-CswYgYeu.js → xychartDiagram-S5SC5T6Z-CfH57l0v.js} +2 -2
  94. package/package.json +4 -4
  95. package/src/__tests__/host-imports.test.ts +1 -0
  96. package/src/components/app-config/ai-config.tsx +6 -2
  97. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  98. package/src/components/chat/chat-panel.tsx +3 -7
  99. package/src/components/chat/chat-utils.ts +4 -8
  100. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  101. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  102. package/src/components/data-table/__tests__/export-actions.test.tsx +434 -6
  103. package/src/components/data-table/charts/charts.tsx +14 -19
  104. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  105. package/src/components/data-table/columns.tsx +2 -1
  106. package/src/components/data-table/context-menu.tsx +36 -20
  107. package/src/components/data-table/date-popover.tsx +6 -4
  108. package/src/components/data-table/export-actions.tsx +267 -163
  109. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +2 -2
  110. package/src/components/data-table/schemas.ts +14 -36
  111. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  112. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +24 -0
  113. package/src/components/editor/actions/pair-with-agent-modal.tsx +18 -15
  114. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  115. package/src/components/editor/cell/CreateCellButton.tsx +8 -10
  116. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  117. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  118. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  119. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  120. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  121. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
  122. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  123. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +12 -18
  124. package/src/components/editor/connections/components.tsx +1 -1
  125. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  126. package/src/components/editor/controls/command-palette.tsx +22 -10
  127. package/src/components/editor/dynamic-favicon.tsx +21 -40
  128. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +12 -23
  129. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  130. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  131. package/src/components/editor/file-tree/file-explorer.tsx +10 -5
  132. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  133. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  134. package/src/components/editor/output/MarimoTracebackOutput.tsx +1 -1
  135. package/src/components/editor/output/console/ConsoleOutput.tsx +8 -18
  136. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  137. package/src/components/icons/large-spinner.tsx +3 -14
  138. package/src/components/pages/home-page.tsx +4 -4
  139. package/src/components/slides/slide-cell-view.tsx +1 -1
  140. package/src/components/tracing/tracing.tsx +2 -2
  141. package/src/core/attachments/state.ts +5 -8
  142. package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
  143. package/src/core/cells/cells.ts +2 -1
  144. package/src/core/cells/pending-delete-service.ts +2 -6
  145. package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
  146. package/src/core/datasets/data-source-discovery.ts +4 -6
  147. package/src/core/edit-app.tsx +2 -2
  148. package/src/core/host/AGENTS.md +11 -2
  149. package/src/core/host/__tests__/attachments.test.ts +56 -0
  150. package/src/core/host/__tests__/document.test.ts +126 -0
  151. package/src/core/host/__tests__/events.test.ts +49 -0
  152. package/src/core/host/__tests__/fixtures.ts +41 -0
  153. package/src/core/host/__tests__/notebook-client.test.ts +496 -0
  154. package/src/core/host/connection.ts +81 -0
  155. package/src/core/host/events.ts +68 -0
  156. package/src/core/host/notebook-client.ts +316 -0
  157. package/src/core/host/resources/attachments.ts +19 -0
  158. package/src/core/host/resources/document.ts +101 -0
  159. package/src/core/hotkeys/actions.ts +2 -4
  160. package/src/core/islands/__tests__/bootstrap.test.ts +0 -26
  161. package/src/core/islands/bootstrap.ts +0 -1
  162. package/src/core/network/connection.ts +4 -2
  163. package/src/core/network/useConnectionNotice.ts +8 -2
  164. package/src/core/run-app.tsx +2 -2
  165. package/src/core/runtime/__tests__/runtime.test.ts +5 -12
  166. package/src/core/runtime/runtime.ts +5 -5
  167. package/src/core/static/files.ts +2 -0
  168. package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -17
  169. package/src/core/websocket/__tests__/{useWebSocket.test.ts → createConnectionTransport.test.ts} +1 -1
  170. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +149 -0
  171. package/src/core/websocket/__tests__/{useMarimoKernelConnection.hook.test.tsx → useNotebookSession.hook.test.tsx} +168 -253
  172. package/src/core/websocket/connection-state.ts +70 -0
  173. package/src/core/websocket/state.ts +7 -0
  174. package/src/core/websocket/transports/__tests__/sse.test.ts +18 -4
  175. package/src/core/websocket/transports/create.ts +31 -0
  176. package/src/core/websocket/transports/sse.ts +14 -2
  177. package/src/core/websocket/transports/transport.ts +12 -23
  178. package/src/core/websocket/types.ts +9 -39
  179. package/src/core/websocket/useNotebookClient.ts +78 -0
  180. package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +53 -233
  181. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  182. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  183. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  184. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  185. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  186. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  187. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  188. package/src/hooks/useAsyncData.ts +3 -3
  189. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  190. package/src/hooks/useDebounce.ts +5 -8
  191. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  192. package/src/hooks/useDelayElapsed.ts +14 -10
  193. package/src/hooks/useElapsedTime.ts +3 -3
  194. package/src/hooks/useInputHistory.ts +4 -7
  195. package/src/hooks/useInternalStateWithSync.ts +6 -6
  196. package/src/hooks/useResizeObserver.ts +4 -7
  197. package/src/hooks/useScript.ts +67 -70
  198. package/src/hooks/useStateForKey.ts +93 -0
  199. package/src/plugins/core/registerReactComponent.tsx +1 -1
  200. package/src/plugins/impl/DataTablePlugin.tsx +14 -15
  201. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  202. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  203. package/src/plugins/impl/FormPlugin.tsx +1 -1
  204. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  205. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +201 -1
  206. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  207. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  208. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
  209. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  210. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  211. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  212. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  213. package/src/plugins/impl/chat/chat-ui.tsx +10 -9
  214. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  215. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  216. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  217. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  218. package/src/plugins/impl/panel/PanelPlugin.tsx +15 -16
  219. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  220. package/src/plugins/impl/panel/utils.ts +17 -0
  221. package/src/plugins/impl/vega/make-selectable.ts +3 -6
  222. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  223. package/src/stories/editor.stories.tsx +24 -17
  224. package/src/utils/__tests__/arrays.test.ts +35 -0
  225. package/src/utils/__tests__/numbers.test.ts +48 -1
  226. package/src/utils/__tests__/objects.test.ts +21 -0
  227. package/src/utils/arrays.ts +10 -0
  228. package/src/utils/numbers.ts +28 -17
  229. package/src/utils/objects.ts +3 -7
  230. package/src/utils/once.ts +19 -19
  231. package/dist/any-language-editor-DZ_YTVus.js +0 -76
  232. package/dist/mermaid-HWGCJPDP-ByCsZnYF.js +0 -2
  233. package/dist/precisionRound-DMIVQrt2.js +0 -97
  234. package/src/core/host/__tests__/protocol.test.ts +0 -214
  235. package/src/core/host/protocol.ts +0 -1943
  236. package/src/core/websocket/useWebSocket.tsx +0 -88
@@ -0,0 +1,31 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { createPyodideConnection } from "../../wasm/bridge";
4
+ import { isWasm } from "../../wasm/utils";
5
+ import { BasicTransport } from "./basic";
6
+ import { SseTransport } from "./sse";
7
+ import { WsTransport } from "./ws";
8
+ import type { IConnectionTransport } from "./transport";
9
+
10
+ export interface ConnectionTransportOptions {
11
+ url: () => string;
12
+ static: boolean;
13
+ transportType: "websocket" | "sse";
14
+ headers: () => Record<string, string>;
15
+ }
16
+
17
+ export function createConnectionTransport(
18
+ options: ConnectionTransportOptions,
19
+ ): IConnectionTransport {
20
+ if (options.static) {
21
+ return BasicTransport.empty();
22
+ }
23
+ if (isWasm()) {
24
+ return createPyodideConnection();
25
+ }
26
+ // Read the URL lazily to follow runtime redirects.
27
+ if (options.transportType === "sse") {
28
+ return new SseTransport(options.url, options.headers);
29
+ }
30
+ return new WsTransport(options.url);
31
+ }
@@ -218,16 +218,20 @@ export class SseTransport implements IConnectionTransport {
218
218
  const decoder = new TextDecoder();
219
219
  let buffer = "";
220
220
  let eventName: string | null = null;
221
+ let eventId: string | null = null;
221
222
  let dataLines: string[] = [];
222
223
 
223
224
  const dispatch = (): ServerClose | null => {
224
225
  if (dataLines.length === 0) {
225
226
  eventName = null;
227
+ eventId = null;
226
228
  return null;
227
229
  }
228
230
  const data = dataLines.join("\n");
229
231
  const name = eventName;
232
+ const id = eventId;
230
233
  eventName = null;
234
+ eventId = null;
231
235
  dataLines = [];
232
236
  if (name === "close") {
233
237
  return parseServerClose(data);
@@ -236,9 +240,15 @@ export class SseTransport implements IConnectionTransport {
236
240
  // (rather than on open, so a server that accepts and immediately
237
241
  // closes cannot retry forever).
238
242
  this.retryCount = 0;
243
+ // Normalize SSE fields into the same envelope as host WebSockets.
244
+ // Leave the payload text intact for the client's JSON decoder.
245
+ const envelope =
246
+ name === null
247
+ ? data
248
+ : `{"event":${JSON.stringify(name)},"data":${data}${id === null ? "" : `,"id":${JSON.stringify(id)}`}}`;
239
249
  this.subscriptions.notify(
240
250
  "message",
241
- new MessageEvent("message", { data }),
251
+ new MessageEvent("message", { data: envelope }),
242
252
  );
243
253
  return null;
244
254
  };
@@ -284,8 +294,10 @@ export class SseTransport implements IConnectionTransport {
284
294
  dataLines.push(fieldValue);
285
295
  } else if (field === "event") {
286
296
  eventName = fieldValue;
297
+ } else if (field === "id") {
298
+ eventId = fieldValue;
287
299
  }
288
- // `id` and `retry` fields are unused.
300
+ // The host client owns the cursor; `retry` does not override our policy.
289
301
  }
290
302
  if (cursor > 0) {
291
303
  buffer = buffer.slice(cursor);
@@ -1,30 +1,19 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- export type ConnectionEvent = "open" | "close" | "message" | "error";
3
2
 
4
- export interface IConnectionTransportMap {
5
- open: WebSocketEventMap["open"];
6
- message: WebSocketEventMap["message"];
7
- close: WebSocketEventMap["close"];
8
- error: WebSocketEventMap["error"];
9
- }
10
-
11
- export type ConnectionTransportCallback<T extends ConnectionEvent> = (
12
- data: IConnectionTransportMap[T],
13
- ) => void;
3
+ import type {
4
+ NotebookTransport,
5
+ ConnectionEvent,
6
+ ConnectionTransportCallback,
7
+ IConnectionTransportMap,
8
+ } from "../../host/connection";
9
+ export type {
10
+ ConnectionEvent,
11
+ ConnectionTransportCallback,
12
+ IConnectionTransportMap,
13
+ } from "../../host/connection";
14
14
 
15
- export interface IConnectionTransport {
16
- reconnect(code?: number | undefined, reason?: string | undefined): void;
17
- close(): void;
15
+ export interface IConnectionTransport extends NotebookTransport {
18
16
  send(data: string | ArrayBuffer | Blob | ArrayBufferView<ArrayBuffer>): void;
19
- addEventListener<T extends ConnectionEvent>(
20
- event: T,
21
- callback: ConnectionTransportCallback<T>,
22
- ): void;
23
- removeEventListener<T extends ConnectionEvent>(
24
- event: T,
25
- callback: ConnectionTransportCallback<T>,
26
- ): void;
27
- readyState: WebSocket["readyState"];
28
17
  }
29
18
 
30
19
  export class ConnectionSubscriptions {
@@ -1,47 +1,17 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
+ import type { ConnectionStatus as HostConnectionStatus } from "../host/connection";
3
4
  import type { NotificationMessageData } from "../kernel/messages";
4
5
 
6
+ export {
7
+ ConnectionState,
8
+ ConnectionClosedReason as WebSocketClosedReason,
9
+ } from "../host/connection";
10
+
5
11
  export type ConnectionPhase =
6
12
  | NotificationMessageData<"startup-progress">["phase"]
7
13
  | "reconnecting";
8
14
 
9
- export const ConnectionState = {
10
- NOT_STARTED: "NOT_STARTED",
11
- CONNECTING: "CONNECTING",
12
- OPEN: "OPEN",
13
- CLOSING: "CLOSING",
14
- CLOSED: "CLOSED",
15
- } as const;
16
-
17
- export type ConnectionState =
18
- (typeof ConnectionState)[keyof typeof ConnectionState];
19
-
20
- export const WebSocketClosedReason = {
21
- KERNEL_DISCONNECTED: "KERNEL_DISCONNECTED",
22
- KERNEL_STARTUP_ERROR: "KERNEL_STARTUP_ERROR",
23
- } as const;
24
-
25
- export type WebSocketClosedReason =
26
- (typeof WebSocketClosedReason)[keyof typeof WebSocketClosedReason];
27
-
28
- export type ConnectionStatus =
29
- | {
30
- state: typeof ConnectionState.CLOSED;
31
- code: WebSocketClosedReason;
32
- /**
33
- * Human-readable reason for closing the connection.
34
- */
35
- reason: string;
36
- phase?: ConnectionPhase;
37
- }
38
- | {
39
- state: typeof ConnectionState.CONNECTING;
40
- phase?: ConnectionPhase;
41
- }
42
- | {
43
- state:
44
- | typeof ConnectionState.OPEN
45
- | typeof ConnectionState.CLOSING
46
- | typeof ConnectionState.NOT_STARTED;
47
- };
15
+ export type ConnectionStatus = HostConnectionStatus & {
16
+ phase?: ConnectionPhase;
17
+ };
@@ -0,0 +1,78 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { useStore } from "jotai";
4
+ import { useEffect, useMemo } from "react";
5
+ import useEvent from "react-use-event-hook";
6
+ import { jsonParseWithSpecialChar } from "@/utils/json/json-parser";
7
+ import type {
8
+ NotificationMessage,
9
+ NotificationPayload,
10
+ } from "../kernel/messages";
11
+ import {
12
+ createNotebookClient,
13
+ type NotebookClient,
14
+ } from "../host/notebook-client";
15
+ import { classifyCloseEvent } from "./connection-state";
16
+ import { notebookClientAtom } from "./state";
17
+ import {
18
+ createConnectionTransport,
19
+ type ConnectionTransportOptions,
20
+ } from "./transports/create";
21
+
22
+ type Options = ConnectionTransportOptions & {
23
+ waitUntilReady: () => Promise<void>;
24
+ checkHealth: () => Promise<boolean>;
25
+ onNotification: (notification: NotificationPayload) => void;
26
+ };
27
+
28
+ export function useNotebookClient(options: Options): NotebookClient {
29
+ const store = useStore();
30
+ const { static: isStatic, transportType } = options;
31
+ const controller = useMemo(() => createNotebookClient(store), [store]);
32
+ const url = useEvent(options.url);
33
+ const headers = useEvent(options.headers);
34
+ const waitUntilReady = useEvent(options.waitUntilReady);
35
+ const checkHealth = useEvent(options.checkHealth);
36
+ const onNotification = useEvent(options.onNotification);
37
+
38
+ useEffect(() => {
39
+ const lifetime = new AbortController();
40
+ store.set(notebookClientAtom, controller.client);
41
+ controller.connect({
42
+ createTransport: (url) =>
43
+ createConnectionTransport({
44
+ url,
45
+ headers,
46
+ static: isStatic,
47
+ transportType,
48
+ }),
49
+ url,
50
+ signal: lifetime.signal,
51
+ waitUntilReady,
52
+ checkHealth,
53
+ // The host validates the envelope, not the native notification contents.
54
+ onNotification: (notification) =>
55
+ onNotification({ data: notification as NotificationMessage }),
56
+ decodeJson: jsonParseWithSpecialChar,
57
+ classifyClose: classifyCloseEvent,
58
+ });
59
+ return () => {
60
+ lifetime.abort();
61
+ if (store.get(notebookClientAtom) === controller.client) {
62
+ store.set(notebookClientAtom, null);
63
+ }
64
+ };
65
+ }, [
66
+ controller,
67
+ store,
68
+ isStatic,
69
+ transportType,
70
+ url,
71
+ headers,
72
+ waitUntilReady,
73
+ checkHealth,
74
+ onNotification,
75
+ ]);
76
+
77
+ return controller.client;
78
+ }
@@ -1,11 +1,10 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import { invalidatePackageData } from "@/core/packages/package-data";
4
- import { useAtom, useAtomValue, useSetAtom } from "jotai";
5
- import { useRef } from "react";
4
+ import { useAtom, useAtomValue, useSetAtom, useStore } from "jotai";
5
+ import { useLayoutEffect } from "react";
6
6
  import { useErrorBoundary } from "react-error-boundary";
7
7
  import { toast } from "@/components/ui/use-toast";
8
- import { attachmentsAtom } from "@/core/attachments/state";
9
8
  import { getNotebook, useCellActions } from "@/core/cells/cells";
10
9
  import { applyTransactionChanges } from "@/core/cells/document-changes";
11
10
  import { AUTOCOMPLETER } from "@/core/codemirror/completion/Autocompleter";
@@ -13,8 +12,7 @@ import type {
13
12
  NotificationMessageData,
14
13
  NotificationPayload,
15
14
  } from "@/core/kernel/messages";
16
- import { TRANSPORT_EXHAUSTED_REASON } from "@/core/websocket/transports/ws";
17
- import { useConnectionTransport } from "@/core/websocket/useWebSocket";
15
+ import { useNotebookClient } from "@/core/websocket/useNotebookClient";
18
16
  import { renderHTML } from "@/plugins/core/RenderHTML";
19
17
  import {
20
18
  handleWidgetMessage,
@@ -22,11 +20,7 @@ import {
22
20
  } from "@/plugins/impl/anywidget/registry";
23
21
  import { logNever } from "@/utils/assertNever";
24
22
  import { prettyError } from "@/utils/errors";
25
- import {
26
- type JsonString,
27
- safeExtractSetUIElementMessageBuffers,
28
- } from "@/utils/json/base64";
29
- import { jsonParseWithSpecialChar } from "@/utils/json/json-parser";
23
+ import { safeExtractSetUIElementMessageBuffers } from "@/utils/json/base64";
30
24
  import { Logger } from "@/utils/Logger";
31
25
  import { reloadSafe } from "@/utils/reload-safe";
32
26
  import { useAlertActions } from "../alerts/state";
@@ -82,104 +76,10 @@ import { useStorageActions } from "../storage/state";
82
76
  import { useVariablesActions } from "../variables/state";
83
77
  import type { VariableName } from "../variables/types";
84
78
  import { isWasm } from "../wasm/utils";
85
- import {
86
- type ConnectionStatus,
87
- WebSocketClosedReason,
88
- ConnectionState,
89
- } from "./types";
79
+ import { WebSocketClosedReason, ConnectionState } from "./types";
90
80
 
91
81
  const SUPPORTS_LAZY_KERNELS = true;
92
82
 
93
- // All MARIMO_* reasons except TRANSPORT_EXHAUSTED are emitted by the backend
94
- // (marimo/_server/api/endpoints/ws_endpoint.py and ws/*.py). Keep in sync with
95
- // the backend literals. Over the SSE transport these arrive as in-band
96
- // `close` events rather than WebSocket close frames, so every terminal
97
- // reason must be classified here — an unclassified reason falls into the
98
- // retry path.
99
- export type CloseReason =
100
- | "MARIMO_NO_FILE_KEY"
101
- | "MARIMO_NO_SESSION_ID"
102
- | "MARIMO_NO_SESSION"
103
- | "MARIMO_SHUTDOWN"
104
- | "MARIMO_KERNEL_STARTUP_ERROR"
105
- | "MARIMO_UNAUTHORIZED"
106
- | "MARIMO_KIOSK_NOT_ALLOWED"
107
- | typeof TRANSPORT_EXHAUSTED_REASON;
108
-
109
- export type CloseDecision =
110
- | { kind: "terminal"; status: ConnectionStatus; closeTransport: boolean }
111
- | { kind: "gave-up"; status: ConnectionStatus }
112
- | { kind: "retry"; status: ConnectionStatus };
113
-
114
- export function classifyCloseEvent(event: { reason?: string }): CloseDecision {
115
- switch (event.reason as CloseReason | undefined) {
116
- case TRANSPORT_EXHAUSTED_REASON:
117
- return {
118
- kind: "gave-up",
119
- status: {
120
- state: ConnectionState.CLOSED,
121
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
122
- reason: "kernel not found",
123
- },
124
- };
125
- case "MARIMO_NO_FILE_KEY":
126
- case "MARIMO_NO_SESSION_ID":
127
- case "MARIMO_NO_SESSION":
128
- case "MARIMO_SHUTDOWN":
129
- return {
130
- kind: "terminal",
131
- status: {
132
- state: ConnectionState.CLOSED,
133
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
134
- reason: "kernel not found",
135
- },
136
- closeTransport: true,
137
- };
138
- case "MARIMO_UNAUTHORIZED":
139
- return {
140
- kind: "terminal",
141
- status: {
142
- state: ConnectionState.CLOSED,
143
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
144
- reason: "not authorized",
145
- },
146
- closeTransport: true,
147
- };
148
- case "MARIMO_KIOSK_NOT_ALLOWED":
149
- return {
150
- kind: "terminal",
151
- status: {
152
- state: ConnectionState.CLOSED,
153
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
154
- reason: "kiosk mode is not available for this session",
155
- },
156
- closeTransport: true,
157
- };
158
- case "MARIMO_KERNEL_STARTUP_ERROR":
159
- return {
160
- kind: "terminal",
161
- status: {
162
- state: ConnectionState.CLOSED,
163
- code: WebSocketClosedReason.KERNEL_STARTUP_ERROR,
164
- reason: "Failed to start kernel sandbox",
165
- },
166
- closeTransport: true,
167
- };
168
- default:
169
- // Empty/undefined reasons are normal transient closes. Anything else is
170
- // an unknown server reason; warn so a new MARIMO_* reason doesn't fall
171
- // silently into the retry path.
172
- if (event.reason) {
173
- logNever(event.reason as never);
174
- }
175
- }
176
-
177
- return {
178
- kind: "retry",
179
- status: { state: ConnectionState.CONNECTING },
180
- };
181
- }
182
-
183
83
  function getExistingCells(): CellData[] | undefined {
184
84
  if (!SUPPORTS_LAZY_KERNELS) {
185
85
  return undefined;
@@ -191,16 +91,12 @@ function getExistingCells(): CellData[] | undefined {
191
91
  );
192
92
  }
193
93
 
194
- /**
195
- * Creates a connection to the Marimo kernel and handles incoming messages.
196
- */
197
- export function useMarimoKernelConnection(opts: {
94
+ /** Coordinate session startup and apply notifications to the editor. */
95
+ export function useNotebookSession(opts: {
198
96
  sessionId: SessionId;
199
97
  autoInstantiate: boolean;
200
98
  setCells: (cells: CellData[], layout: LayoutState) => void;
201
99
  }) {
202
- // Track whether we want to try reconnecting.
203
- const shouldTryReconnecting = useRef<boolean>(true);
204
100
  const { autoInstantiate, sessionId, setCells } = opts;
205
101
  const { showBoundary } = useErrorBoundary();
206
102
 
@@ -237,7 +133,6 @@ export function useMarimoKernelConnection(opts: {
237
133
  } = useAlertActions();
238
134
  const setKioskMode = useSetAtom(kioskModeAtom);
239
135
  const setStableSessionId = useSetAtom(stableSessionIdAtom);
240
- const setAttachments = useSetAtom(attachmentsAtom);
241
136
  const setCapabilities = useSetAtom(capabilitiesAtom);
242
137
  const runtimeManager = useRuntimeManager();
243
138
  const transportType = useAtomValue(connectionTransportTypeAtom);
@@ -248,15 +143,11 @@ export function useMarimoKernelConnection(opts: {
248
143
  filterFromVariables: filterStorageFromVariables,
249
144
  } = useStorageActions();
250
145
 
251
- const handleMessage = (e: MessageEvent<JsonString<NotificationPayload>>) => {
252
- const msg = jsonParseWithSpecialChar(e.data);
146
+ const handleNotification = (msg: NotificationPayload) => {
253
147
  switch (msg.data.op) {
254
148
  case "reload":
255
149
  reloadSafe();
256
150
  return;
257
- case "attachments":
258
- setAttachments(msg.data.attachments);
259
- return;
260
151
  case "startup-progress": {
261
152
  const { phase } = msg.data;
262
153
  updateStartupProgress(msg.data);
@@ -288,7 +179,6 @@ export function useMarimoKernelConnection(opts: {
288
179
  existingCells,
289
180
  });
290
181
  if (!msg.data.resumed) {
291
- setAttachments([]);
292
182
  // A freshly started kernel may expose a different environment
293
183
  // (e.g. new env vars); re-run discovery instead of reusing stale
294
184
  // suggestions from the previous kernel instance.
@@ -511,109 +401,54 @@ export function useMarimoKernelConnection(opts: {
511
401
  }
512
402
  };
513
403
 
514
- const tryReconnecting = (code?: number, reason?: string) => {
515
- // If not properly gated, we could try reconnecting forever if the
516
- // issue is not transient. So we want to try reconnecting only once after an
517
- // open connection is closed.
518
- if (shouldTryReconnecting.current) {
519
- shouldTryReconnecting.current = false;
520
- ws.reconnect(code, reason);
521
- }
404
+ const store = useStore();
405
+ const onMessageError = (error: unknown) => {
406
+ toast({
407
+ title: "Failed to handle message",
408
+ description: prettyError(error),
409
+ variant: "danger",
410
+ });
522
411
  };
523
-
524
- // Manual reconnect. Probes /health first to fail fast when the runtime
525
- // is unreachable, instead of waiting on partysocket's retry budget.
526
- const reconnect = async () => {
527
- if (
528
- ws.readyState === WebSocket.OPEN ||
529
- ws.readyState === WebSocket.CONNECTING
530
- ) {
531
- return;
532
- }
533
- shouldTryReconnecting.current = true;
534
- setKernelStartupError(null);
535
- setConnection({ state: ConnectionState.CONNECTING });
536
- const healthy = await runtimeManager.reconcileFromHealth();
537
- if (!healthy) {
538
- shouldTryReconnecting.current = false;
539
- setConnection({
540
- state: ConnectionState.CLOSED,
541
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
542
- reason: "kernel not found",
543
- });
544
- return;
545
- }
546
- ws.reconnect();
547
- };
548
-
549
- const ws = useConnectionTransport({
412
+ const notebook = useNotebookClient({
550
413
  static: isStaticNotebook(),
551
414
  transportType,
552
- /**
553
- * Unique URL for this session.
554
- */
555
415
  url: () =>
556
416
  transportType === "sse"
557
417
  ? runtimeManager.getSseURL(sessionId).toString()
558
418
  : runtimeManager.getWsURL(sessionId).toString(),
559
- /**
560
- * Auth headers for the SSE transport. WebSockets cannot send headers
561
- * and instead put the access token in the URL.
562
- */
419
+ // SSE uses headers for auth. WebSockets carry the token in the URL.
563
420
  headers: () => runtimeManager.headers(),
564
-
565
- /**
566
- * The transport is open; kernel-ready establishes session readiness.
567
- */
568
- onOpen: async () => {
569
- updateStartupProgress(null);
570
- setAttachments([]);
571
- // If we are open, we can reset our reconnecting flag.
572
- shouldTryReconnecting.current = true;
573
- },
574
-
575
- /**
576
- * Wait to connect, in case the remote kernel still starting up.
577
- */
578
- waitToConnect: async () => {
579
- if (isStaticNotebook()) {
580
- return;
421
+ waitUntilReady: async () => {
422
+ if (!isStaticNotebook() && !isWasm() && !runtimeManager.isSameOrigin) {
423
+ await runtimeManager.waitForHealthy();
581
424
  }
582
- if (isWasm()) {
583
- return;
584
- }
585
-
586
- if (runtimeManager.isSameOrigin) {
587
- return;
588
- }
589
- await runtimeManager.waitForHealthy();
590
425
  },
591
-
592
- /**
593
- * Handle messages sent by the kernel.
594
- */
595
- onMessage: (e) => {
426
+ checkHealth: () => runtimeManager.reconcileFromHealth(),
427
+ onNotification: (notification) => {
596
428
  try {
597
- handleMessage(e);
429
+ handleNotification(notification);
598
430
  } catch (error) {
599
- Logger.error("Failed to handle message", e.data, error);
600
- toast({
601
- title: "Failed to handle message",
602
- description: prettyError(error),
603
- variant: "danger",
604
- });
431
+ Logger.error("Failed to handle notification", notification, error);
432
+ onMessageError(error);
605
433
  }
606
434
  },
435
+ });
607
436
 
608
- /**
609
- * Handle a close event. We may want to reconnect.
610
- */
611
- onClose: (e) => {
612
- Logger.warn("WebSocket closed", e.code, e.reason);
613
- setAttachments([]);
614
- const decision = classifyCloseEvent(e);
437
+ // Subscribe before passive connection setup: static/WASM transports can emit synchronously.
438
+ useLayoutEffect(() => {
439
+ const updateConnection = () => {
440
+ const status = store.get(notebook.connectionAtom);
441
+ if (status.state === ConnectionState.NOT_STARTED) {
442
+ return;
443
+ }
444
+ if (status.state === ConnectionState.OPEN) {
445
+ updateStartupProgress(null);
446
+ return; // kernel-ready, not the socket, establishes session readiness.
447
+ }
448
+ if (status.state === ConnectionState.CONNECTING) {
449
+ setKernelStartupError(null);
450
+ }
615
451
  setConnection((previous) => {
616
- const status = decision.status;
617
452
  if (
618
453
  status.state === ConnectionState.CLOSED &&
619
454
  status.code === WebSocketClosedReason.KERNEL_STARTUP_ERROR &&
@@ -632,32 +467,17 @@ export function useMarimoKernelConnection(opts: {
632
467
  }
633
468
  return status;
634
469
  });
635
- if (decision.kind === "terminal" && decision.closeTransport) {
636
- ws.close(); // close to prevent reconnecting
637
- return;
638
- }
639
- if (decision.kind === "retry") {
640
- // Session should be valid
641
- // - browser tab might have been closed or re-opened
642
- // - computer might have just woken from sleep
643
- tryReconnecting(e.code, e.reason);
644
- }
645
- },
646
-
647
- /**
648
- * When we encounter an error, we should close the connection.
649
- */
650
- onError: (e) => {
651
- Logger.warn("WebSocket error", e);
652
- setAttachments([]);
653
- setConnection({
654
- state: ConnectionState.CLOSED,
655
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
656
- reason: "kernel not found",
657
- });
658
- tryReconnecting();
659
- },
660
- });
661
-
662
- return { connection, reconnect };
470
+ };
471
+ const unsubscribe = store.sub(notebook.connectionAtom, updateConnection);
472
+ updateConnection();
473
+ return unsubscribe;
474
+ }, [
475
+ notebook,
476
+ store,
477
+ setConnection,
478
+ setKernelStartupError,
479
+ updateStartupProgress,
480
+ ]);
481
+
482
+ return { notebook, connection, reconnect: notebook.reconnect };
663
483
  }