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

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-BEfUX0zz.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-CA0UOEgk.js} +6 -6
  19. package/dist/{common-B7_ZSg0P.js → common-BgoPSdwN.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-CRjm80Ab.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-CZA7i0Ir.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-Bd52MLtf.js} +370 -368
  41. package/dist/{glide-utils-DJyqh0EM.js → glide-utils-Bt8_pBa2.js} +16 -16
  42. package/dist/{globals-Cj8iqMEp.js → globals-BvANmUu6.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-C7ffluCS.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-Dfuz8zQ_.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
@@ -21,14 +21,9 @@ import {
21
21
  vi,
22
22
  } from "vitest";
23
23
 
24
- vi.mock("@/core/websocket/useWebSocket", async () => {
25
- const actual =
26
- await vi.importActual<typeof import("../useWebSocket")>("../useWebSocket");
27
- return {
28
- ...actual,
29
- useConnectionTransport: vi.fn(),
30
- };
31
- });
24
+ vi.mock("../transports/create", () => ({
25
+ createConnectionTransport: vi.fn(),
26
+ }));
32
27
 
33
28
  vi.mock("@/core/runtime/config", async () => {
34
29
  const actual = await vi.importActual<typeof import("@/core/runtime/config")>(
@@ -41,26 +36,27 @@ vi.mock("@/core/runtime/config", async () => {
41
36
  });
42
37
 
43
38
  import { MockNotebook } from "@/__mocks__/notebook";
39
+ import { Logger } from "@/utils/Logger";
40
+ import { Deferred } from "@/utils/Deferred";
44
41
  import { cellId } from "@/__tests__/branded";
45
42
  import { alertAtom, getPackageAlert } from "@/core/alerts/state";
46
- import {
47
- type Attachment,
48
- attachmentsAtom,
49
- agentAttachmentsAtom,
50
- } from "@/core/attachments/state";
51
43
  import { notebookAtom } from "@/core/cells/cells";
52
44
  import { AppConfigSchema } from "@/core/config/config-schema";
53
45
  import { ConnectionNotice } from "@/components/editor/alerts/connection-notice";
54
46
  import { kernelStartupErrorAtom } from "@/core/errors/state";
47
+ import type { RunEvent } from "@marimo-team/client/protocol";
55
48
  import type { NotificationPayload } from "@/core/kernel/messages";
56
49
  import { useRuntimeManager } from "@/core/runtime/config";
57
50
  import { initialRunCompletedAtom } from "../../kernel/state";
58
51
  import { connectionAtom, startupProgressAtom } from "../../network/connection";
59
- import type { SessionId } from "../../kernel/session";
52
+ import { generateSessionId } from "../../kernel/session";
53
+ import { RuntimeManager } from "../../runtime/runtime";
60
54
  import { WebSocketClosedReason, ConnectionState } from "../types";
61
55
  import type { IConnectionTransport } from "../transports/transport";
62
- import { useMarimoKernelConnection } from "../useMarimoKernelConnection";
63
- import { useConnectionTransport } from "../useWebSocket";
56
+ import { useNotebookSession } from "../useNotebookSession";
57
+ import { createConnectionTransport } from "../transports/create";
58
+
59
+ const SESSION_ID = generateSessionId();
64
60
 
65
61
  interface MockTransport {
66
62
  readyState: 0 | 1 | 2 | 3;
@@ -84,16 +80,47 @@ function makeTransport(
84
80
  };
85
81
  }
86
82
 
83
+ function getTransportHandlers() {
84
+ const result = vi.mocked(createConnectionTransport).mock.results.at(-1);
85
+ if (!result || result.type !== "return") {
86
+ throw new Error("No connection transport was created");
87
+ }
88
+ const transport = result.value;
89
+ function emit(
90
+ event: "open" | "close" | "error" | "message",
91
+ message: Event | CloseEvent | MessageEvent,
92
+ ) {
93
+ const callback = vi
94
+ .mocked(transport.addEventListener)
95
+ .mock.calls.find(([name]) => name === event)?.[1];
96
+ callback?.(message);
97
+ }
98
+ return {
99
+ onError: (event: Event) => emit("error", event),
100
+ onOpen: (event: Event) => emit("open", event),
101
+ onClose: (event: CloseEvent) => emit("close", event),
102
+ onMessage: (event: MessageEvent) => emit("message", event),
103
+ };
104
+ }
105
+
87
106
  function makeRuntimeManager(
88
107
  reconcileFromHealth = vi.fn<() => Promise<boolean>>().mockResolvedValue(true),
89
108
  ) {
90
- return {
109
+ const runtime = new RuntimeManager(
110
+ { url: "http://localhost", lazy: true },
111
+ true,
112
+ );
113
+ vi.spyOn(runtime, "reconcileFromHealth").mockImplementation(
91
114
  reconcileFromHealth,
92
- probeHealth: vi.fn().mockResolvedValue(true),
93
- getWsURL: () => new URL("ws://localhost/ws"),
94
- waitForHealthy: vi.fn().mockResolvedValue(undefined),
95
- isSameOrigin: true,
96
- };
115
+ );
116
+ vi.spyOn(runtime, "probeHealth").mockResolvedValue(true);
117
+ vi.spyOn(runtime, "getWsURL").mockReturnValue(new URL("ws://localhost/ws"));
118
+ // Keep the initial connection pending; these tests exercise session retries.
119
+ vi.spyOn(runtime, "waitForHealthy").mockReturnValue(
120
+ new Deferred<void>().promise,
121
+ );
122
+ vi.spyOn(runtime, "isSameOrigin", "get").mockReturnValue(false);
123
+ return runtime;
97
124
  }
98
125
 
99
126
  function renderConnectionHook(store: ReturnType<typeof createStore>) {
@@ -104,16 +131,16 @@ function renderConnectionHook(store: ReturnType<typeof createStore>) {
104
131
  );
105
132
  return renderHook(
106
133
  () =>
107
- useMarimoKernelConnection({
108
- sessionId: "test-session" as SessionId,
134
+ useNotebookSession({
135
+ sessionId: SESSION_ID,
109
136
  autoInstantiate: false,
110
- setCells: () => {},
137
+ setCells: vi.fn(),
111
138
  }),
112
139
  { wrapper },
113
140
  );
114
141
  }
115
142
 
116
- describe("useMarimoKernelConnection.reconnect()", () => {
143
+ describe("useNotebookSession.reconnect()", () => {
117
144
  let transport: MockTransport;
118
145
  let isHealthy: Mock<() => Promise<boolean>>;
119
146
  let store: ReturnType<typeof createStore>;
@@ -127,12 +154,8 @@ describe("useMarimoKernelConnection.reconnect()", () => {
127
154
  code: WebSocketClosedReason.KERNEL_DISCONNECTED,
128
155
  reason: "kernel not found",
129
156
  });
130
- vi.mocked(useConnectionTransport).mockReturnValue(transport);
131
- vi.mocked(useRuntimeManager).mockReturnValue(
132
- makeRuntimeManager(isHealthy) as unknown as ReturnType<
133
- typeof useRuntimeManager
134
- >,
135
- );
157
+ vi.mocked(createConnectionTransport).mockReturnValue(transport);
158
+ vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager(isHealthy));
136
159
  });
137
160
 
138
161
  function renderUseHook() {
@@ -188,166 +211,117 @@ describe("useMarimoKernelConnection.reconnect()", () => {
188
211
  });
189
212
  });
190
213
 
191
- describe("useMarimoKernelConnection messages", () => {
192
- it("replaces attachment snapshots, including the last detach", () => {
193
- const store = createStore();
194
- vi.mocked(useConnectionTransport).mockReturnValue(
195
- makeTransport(WebSocket.OPEN),
196
- );
197
- vi.mocked(useRuntimeManager).mockReturnValue(
198
- makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
199
- );
200
- renderConnectionHook(store);
201
- const options = vi.mocked(useConnectionTransport).mock.calls.at(-1)![0];
202
- const agent: Attachment = {
203
- id: "a1",
204
- kind: "agent",
205
- name: "Pi",
206
- since: "2026-10-14T00:00:00Z",
207
- notebook_id: "nb-test",
208
- identity: "pi-thread",
209
- };
210
- const client: Attachment = {
211
- id: "c1",
212
- kind: "client",
213
- name: null,
214
- since: "2026-10-14T00:00:01Z",
215
- notebook_id: "nb-test",
216
- };
217
- for (const attachments of [[agent, client], [client], []]) {
218
- act(() =>
219
- options.onMessage(
220
- new MessageEvent("message", {
221
- data: JSON.stringify({
222
- op: "attachments",
223
- data: { op: "attachments", attachments },
224
- }),
225
- }),
226
- ),
227
- );
228
- expect(store.get(attachmentsAtom)).toEqual(attachments);
229
- }
230
- });
214
+ describe("useNotebookSession messages", () => {
215
+ let store: ReturnType<typeof createStore>;
231
216
 
232
- it("records completion of the initial run", () => {
233
- const store = createStore();
234
- vi.mocked(useConnectionTransport).mockClear();
235
- vi.mocked(useConnectionTransport).mockReturnValue(
217
+ beforeEach(() => {
218
+ store = createStore();
219
+ vi.mocked(createConnectionTransport).mockClear();
220
+ vi.mocked(createConnectionTransport).mockReturnValue(
236
221
  makeTransport(WebSocket.OPEN),
237
222
  );
238
- vi.mocked(useRuntimeManager).mockReturnValue(
239
- makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
240
- );
223
+ vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
241
224
  renderConnectionHook(store);
225
+ });
242
226
 
243
- const options = vi.mocked(useConnectionTransport).mock.calls.at(-1)?.[0];
244
- expect(store.get(initialRunCompletedAtom)).toBe(false);
227
+ function receive(payload: unknown) {
228
+ const options = getTransportHandlers();
245
229
  act(() => {
246
- options?.onMessage(
247
- new MessageEvent("message", {
248
- data: JSON.stringify({
249
- op: "completed-run",
250
- data: { op: "completed-run", run_id: null },
251
- }),
252
- }),
230
+ options.onMessage(
231
+ new MessageEvent("message", { data: JSON.stringify(payload) }),
253
232
  );
254
233
  });
255
- expect(store.get(initialRunCompletedAtom)).toBe(true);
256
- });
257
- });
258
-
259
- describe("attachment snapshots across browser connections", () => {
260
- const agent: Attachment = {
261
- id: "a1",
262
- kind: "agent",
263
- name: "Pi",
264
- since: "2026-10-14T00:00:00Z",
265
- notebook_id: "nb-test",
266
- identity: "pi-thread",
267
- };
268
-
269
- function connectedSession() {
270
- const store = createStore();
271
- store.set(connectionAtom, { state: ConnectionState.OPEN });
272
- store.set(attachmentsAtom, [agent]);
273
- const transport = makeTransport(WebSocket.OPEN);
274
- vi.mocked(useConnectionTransport).mockReturnValue(transport);
275
- vi.mocked(useRuntimeManager).mockReturnValue(
276
- makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
277
- );
278
- renderConnectionHook(store);
279
- const options = vi.mocked(useConnectionTransport).mock.calls.at(-1)![0];
280
- const receive = (data: NotificationPayload["data"]) =>
281
- act(() => {
282
- options.onMessage(
283
- new MessageEvent("message", {
284
- data: JSON.stringify({ op: data.op, data }),
285
- }),
286
- );
287
- });
288
- return { store, transport, options, receive };
289
234
  }
290
235
 
291
- it.each([
292
- "MARIMO_NO_FILE_KEY",
293
- "MARIMO_NO_SESSION_ID",
294
- "MARIMO_NO_SESSION",
295
- "MARIMO_SHUTDOWN",
296
- "MARIMO_KERNEL_STARTUP_ERROR",
297
- "MARIMO_UNAUTHORIZED",
298
- "MARIMO_KIOSK_NOT_ALLOWED",
299
- "MARIMO_TRANSPORT_EXHAUSTED",
300
- ])("clears cached attachments on %s", (reason) => {
301
- const { store, transport, options } = connectedSession();
302
- act(() => options.onClose(new CloseEvent("close", { reason })));
303
- expect(store.get(attachmentsAtom)).toEqual([]);
304
- expect(store.get(agentAttachmentsAtom)).toEqual([]);
305
- expect(store.get(connectionAtom).state).toBe(ConnectionState.CLOSED);
306
- expect(transport.reconnect).not.toHaveBeenCalled();
307
- });
308
-
309
- it("clears the snapshot on transport error", () => {
310
- const { store, options } = connectedSession();
311
- act(() => options.onError(new Event("error")));
312
- expect(store.get(attachmentsAtom)).toEqual([]);
313
- expect(store.get(agentAttachmentsAtom)).toEqual([]);
314
- });
315
-
316
- it("waits for a fresh snapshot after a transient reconnect", async () => {
317
- const { store, transport, options, receive } = connectedSession();
318
- act(() => options.onClose(new CloseEvent("close")));
319
- expect(store.get(attachmentsAtom)).toEqual([]);
320
- expect(store.get(connectionAtom).state).toBe(ConnectionState.CONNECTING);
321
- expect(transport.reconnect).toHaveBeenCalledOnce();
322
-
323
- await act(async () => options.onOpen(new Event("open")));
324
- receive({ op: "reconnected" });
325
- expect(store.get(connectionAtom).state).toBe(ConnectionState.OPEN);
326
- expect(store.get(agentAttachmentsAtom)).toEqual([]);
327
-
328
- const refreshed = { ...agent, id: "a2", name: "Codex" };
329
- receive({ op: "attachments", attachments: [refreshed] });
330
- expect(store.get(agentAttachmentsAtom)).toEqual([refreshed]);
331
- receive({ op: "attachments", attachments: [] });
332
- expect(store.get(agentAttachmentsAtom)).toEqual([]);
333
- });
236
+ it.each(["{broken", "null"])(
237
+ "closes a malformed stream and ignores subsequent notifications: %s",
238
+ (data) => {
239
+ receive({ event: "marimo", data: { marimo: { op: "reconnected" } } });
240
+ act(() =>
241
+ getTransportHandlers().onMessage(new MessageEvent("message", { data })),
242
+ );
243
+ expect(store.get(connectionAtom)).toEqual({
244
+ state: ConnectionState.CLOSED,
245
+ code: "KERNEL_DISCONNECTED",
246
+ reason: "Failed to decode notebook message",
247
+ });
248
+ receive({
249
+ event: "marimo",
250
+ data: {
251
+ marimo: {
252
+ op: "completed-run",
253
+ run_id: "r1",
254
+ outcome: "succeeded",
255
+ error: null,
256
+ },
257
+ },
258
+ });
259
+ expect(store.get(initialRunCompletedAtom)).toBe(false);
260
+ },
261
+ );
334
262
 
335
- it("keeps a fresh snapshot hidden until the session is ready", async () => {
336
- const { store, options, receive } = connectedSession();
337
- act(() => options.onClose(new CloseEvent("close")));
338
- await act(async () => options.onOpen(new Event("open")));
339
- receive({ op: "attachments", attachments: [agent] });
340
- expect(store.get(attachmentsAtom)).toEqual([agent]);
341
- expect(store.get(agentAttachmentsAtom)).toEqual([]);
342
- receive({ op: "reconnected" });
343
- expect(store.get(agentAttachmentsAtom)).toEqual([agent]);
344
- });
263
+ it.each(["session", "host"])(
264
+ "records completion of the initial run from the %s stream",
265
+ (stream) => {
266
+ const notification = {
267
+ op: "completed-run",
268
+ run_id: null,
269
+ outcome: "succeeded",
270
+ error: null,
271
+ } satisfies NotificationPayload["data"];
272
+ expect(store.get(initialRunCompletedAtom)).toBe(false);
273
+ receive(
274
+ stream === "host"
275
+ ? { id: "42", event: "marimo", data: { marimo: notification } }
276
+ : { op: notification.op, data: notification },
277
+ );
278
+ expect(store.get(initialRunCompletedAtom)).toBe(true);
279
+ },
280
+ );
345
281
 
346
- it("invalidates an old snapshot when a new transport opens", async () => {
347
- const { store, options } = connectedSession();
348
- await act(async () => options.onOpen(new Event("open")));
349
- expect(store.get(attachmentsAtom)).toEqual([]);
350
- expect(store.get(agentAttachmentsAtom)).toEqual([]);
282
+ it("ignores other host events before handling the next notification", () => {
283
+ const errors = vi.spyOn(Logger, "error").mockImplementation(vi.fn());
284
+ try {
285
+ receive({ id: "1", event: "ready", data: {} });
286
+ receive({
287
+ id: "2",
288
+ event: "run",
289
+ data: {
290
+ run: {
291
+ id: "r1",
292
+ notebook_id: "n1",
293
+ caused_by: null,
294
+ status: "completed",
295
+ outcome: "succeeded",
296
+ started_at: "2026-10-19T12:00:00Z",
297
+ completed_at: "2026-10-19T12:00:01Z",
298
+ },
299
+ },
300
+ } satisfies RunEvent);
301
+ receive({
302
+ id: "3",
303
+ event: "future-event",
304
+ data: { marimo: { op: "completed-run", run_id: "r1" } },
305
+ });
306
+ receive({ id: "4", event: "another-event", data: null });
307
+ expect(store.get(initialRunCompletedAtom)).toBe(false);
308
+ receive({
309
+ id: "5",
310
+ event: "marimo",
311
+ data: {
312
+ marimo: {
313
+ op: "completed-run",
314
+ run_id: "r1",
315
+ outcome: "succeeded",
316
+ error: null,
317
+ },
318
+ },
319
+ });
320
+ expect(store.get(initialRunCompletedAtom)).toBe(true);
321
+ expect(errors).not.toHaveBeenCalled();
322
+ } finally {
323
+ errors.mockRestore();
324
+ }
351
325
  });
352
326
  });
353
327
 
@@ -356,15 +330,13 @@ it.each(["kernel-ready", "reconnected"])(
356
330
  async (op) => {
357
331
  const store = createStore();
358
332
  store.set(connectionAtom, { state: ConnectionState.CONNECTING });
359
- vi.mocked(useConnectionTransport).mockClear();
360
- vi.mocked(useConnectionTransport).mockReturnValue(
333
+ vi.mocked(createConnectionTransport).mockClear();
334
+ vi.mocked(createConnectionTransport).mockReturnValue(
361
335
  makeTransport(WebSocket.OPEN),
362
336
  );
363
- vi.mocked(useRuntimeManager).mockReturnValue(
364
- makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
365
- );
337
+ vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
366
338
  renderConnectionHook(store);
367
- const options = vi.mocked(useConnectionTransport).mock.calls.at(-1)![0];
339
+ const options = getTransportHandlers();
368
340
 
369
341
  await act(async () => {
370
342
  await options.onOpen(new Event("open"));
@@ -401,59 +373,6 @@ it.each(["kernel-ready", "reconnected"])(
401
373
  },
402
374
  );
403
375
 
404
- it.each([true, false])(
405
- "keeps attachments only when kernel-ready resumes the session: %s",
406
- (resumed) => {
407
- const store = createStore();
408
- const attachments: Attachment[] = [
409
- {
410
- id: "a1",
411
- kind: "agent",
412
- name: "Pi",
413
- since: "2026-10-14T00:00:00Z",
414
- notebook_id: "nb-test",
415
- identity: "pi-thread",
416
- },
417
- ];
418
- store.set(attachmentsAtom, attachments);
419
- vi.mocked(useConnectionTransport).mockReturnValue(
420
- makeTransport(WebSocket.OPEN),
421
- );
422
- vi.mocked(useRuntimeManager).mockReturnValue(
423
- makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
424
- );
425
- renderConnectionHook(store);
426
- const options = vi.mocked(useConnectionTransport).mock.calls.at(-1)![0];
427
- act(() =>
428
- options.onMessage(
429
- new MessageEvent("message", {
430
- data: JSON.stringify({
431
- op: "kernel-ready",
432
- data: {
433
- op: "kernel-ready",
434
- cell_ids: [],
435
- codes: [],
436
- names: [],
437
- configs: [],
438
- layout: null,
439
- resumed,
440
- ui_values: {},
441
- last_executed_code: {},
442
- last_execution_time: {},
443
- app_config: { width: "normal" },
444
- kiosk: false,
445
- capabilities: { terminal: false },
446
- auto_instantiated: true,
447
- consumer_capabilities: { edit: true, interact: true },
448
- },
449
- }),
450
- }),
451
- ),
452
- );
453
- expect(store.get(attachmentsAtom)).toEqual(resumed ? attachments : []);
454
- },
455
- );
456
-
457
376
  describe("connection notice", () => {
458
377
  beforeEach(() => vi.useFakeTimers());
459
378
  afterEach(() => vi.useRealTimers());
@@ -462,15 +381,13 @@ describe("connection notice", () => {
462
381
  const store = createStore();
463
382
  store.set(connectionAtom, { state: ConnectionState.CONNECTING });
464
383
  const transport = makeTransport(WebSocket.OPEN);
465
- vi.mocked(useConnectionTransport).mockReturnValue(transport);
466
- vi.mocked(useRuntimeManager).mockReturnValue(
467
- makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
468
- );
384
+ vi.mocked(createConnectionTransport).mockReturnValue(transport);
385
+ vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
469
386
  const Connection = () => {
470
- const { reconnect } = useMarimoKernelConnection({
471
- sessionId: "test-session" as SessionId,
387
+ const { reconnect } = useNotebookSession({
388
+ sessionId: SESSION_ID,
472
389
  autoInstantiate: false,
473
- setCells: () => {},
390
+ setCells: vi.fn(),
474
391
  });
475
392
  return (
476
393
  <ConnectionNotice
@@ -486,7 +403,7 @@ describe("connection notice", () => {
486
403
  </ErrorBoundary>
487
404
  </JotaiProvider>,
488
405
  );
489
- const options = vi.mocked(useConnectionTransport).mock.calls.at(-1)![0];
406
+ const options = getTransportHandlers();
490
407
  const send = (data: NotificationPayload["data"]) =>
491
408
  act(() => {
492
409
  options.onMessage(
@@ -675,15 +592,13 @@ describe("connection notice", () => {
675
592
 
676
593
  it("replaces environment state from snapshots, then continues live progress", () => {
677
594
  const store = createStore();
678
- vi.mocked(useConnectionTransport).mockClear();
679
- vi.mocked(useConnectionTransport).mockReturnValue(
595
+ vi.mocked(createConnectionTransport).mockClear();
596
+ vi.mocked(createConnectionTransport).mockReturnValue(
680
597
  makeTransport(WebSocket.OPEN),
681
598
  );
682
- vi.mocked(useRuntimeManager).mockReturnValue(
683
- makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
684
- );
599
+ vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
685
600
  renderConnectionHook(store);
686
- const options = vi.mocked(useConnectionTransport).mock.calls.at(-1)?.[0];
601
+ const options = getTransportHandlers();
687
602
  function receive(data: NotificationPayload["data"]) {
688
603
  act(() =>
689
604
  options?.onMessage(
@@ -0,0 +1,70 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import type { CloseDecision } from "../host/connection";
4
+ import { Logger } from "@/utils/Logger";
5
+ import { TRANSPORT_EXHAUSTED_REASON } from "./transports/ws";
6
+ import { ConnectionState, WebSocketClosedReason } from "./types";
7
+
8
+ // Keep server reasons in sync with ws_endpoint.py and ws/*.py.
9
+ // SSE carries the same reasons in close events. Unclassified reasons retry.
10
+ export function classifyCloseEvent(event: { reason?: string }): CloseDecision {
11
+ switch (event.reason) {
12
+ case TRANSPORT_EXHAUSTED_REASON:
13
+ return {
14
+ kind: "gave-up",
15
+ status: {
16
+ state: ConnectionState.CLOSED,
17
+ code: WebSocketClosedReason.KERNEL_DISCONNECTED,
18
+ reason: "kernel not found",
19
+ },
20
+ };
21
+ case "MARIMO_NO_FILE_KEY":
22
+ case "MARIMO_NO_SESSION_ID":
23
+ case "MARIMO_NO_SESSION":
24
+ case "MARIMO_SHUTDOWN":
25
+ return {
26
+ kind: "terminal",
27
+ status: {
28
+ state: ConnectionState.CLOSED,
29
+ code: WebSocketClosedReason.KERNEL_DISCONNECTED,
30
+ reason: "kernel not found",
31
+ },
32
+ };
33
+ case "MARIMO_UNAUTHORIZED":
34
+ return {
35
+ kind: "terminal",
36
+ status: {
37
+ state: ConnectionState.CLOSED,
38
+ code: WebSocketClosedReason.KERNEL_DISCONNECTED,
39
+ reason: "not authorized",
40
+ },
41
+ };
42
+ case "MARIMO_KIOSK_NOT_ALLOWED":
43
+ return {
44
+ kind: "terminal",
45
+ status: {
46
+ state: ConnectionState.CLOSED,
47
+ code: WebSocketClosedReason.KERNEL_DISCONNECTED,
48
+ reason: "kiosk mode is not available for this session",
49
+ },
50
+ };
51
+ case "MARIMO_KERNEL_STARTUP_ERROR":
52
+ return {
53
+ kind: "terminal",
54
+ status: {
55
+ state: ConnectionState.CLOSED,
56
+ code: WebSocketClosedReason.KERNEL_STARTUP_ERROR,
57
+ reason: "Failed to start kernel sandbox",
58
+ },
59
+ };
60
+ default:
61
+ if (event.reason) {
62
+ Logger.warn("Unrecognized connection close reason", event.reason);
63
+ }
64
+ }
65
+
66
+ return {
67
+ kind: "retry",
68
+ status: { state: ConnectionState.CONNECTING },
69
+ };
70
+ }
@@ -0,0 +1,7 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { atom } from "jotai";
4
+ import type { NotebookClient } from "../host/notebook-client";
5
+
6
+ /** The client owned by the mounted notebook session. */
7
+ export const notebookClientAtom = atom<NotebookClient | null>(null);
@@ -90,6 +90,23 @@ describe("SseTransport", () => {
90
90
  expect(messages[1].data).toBe("line1\nline2");
91
91
  });
92
92
 
93
+ it("preserves host event names and cursors without carrying IDs into snapshots", async () => {
94
+ const { stream, push } = createControllableStream();
95
+ fetchMock.mockResolvedValueOnce(sseResponse(stream));
96
+ transport.reconnect();
97
+ await vi.waitFor(() => expect(opens).toHaveLength(1));
98
+
99
+ push("id: cursor-1\nevent: rea");
100
+ push("dy\ndata: {}\n\n: keep-alive\n\nevent: reset\ndata: {}\n\n");
101
+ push('event: marimo\ndata: {"marimo":\ndata: {"op":"reconnected"}}\n\n');
102
+ await vi.waitFor(() => expect(messages).toHaveLength(3));
103
+ expect(messages.map((message) => JSON.parse(message.data))).toEqual([
104
+ { event: "ready", id: "cursor-1", data: {} },
105
+ { event: "reset", data: {} },
106
+ { event: "marimo", data: { marimo: { op: "reconnected" } } },
107
+ ]);
108
+ });
109
+
93
110
  it("ignores keep-alive comments", async () => {
94
111
  const { stream, push } = createControllableStream();
95
112
  fetchMock.mockResolvedValueOnce(sseResponse(stream));
@@ -108,8 +125,6 @@ describe("SseTransport", () => {
108
125
  transport.reconnect();
109
126
  await vi.waitFor(() => expect(opens).toHaveLength(1));
110
127
 
111
- // Stop retrying when the consumer treats the close as terminal,
112
- // mirroring useMarimoKernelConnection's terminal path.
113
128
  transport.addEventListener("close", () => transport.close());
114
129
  push('event: close\ndata: {"code": 1000, "reason": "MARIMO_SHUTDOWN"}\n\n');
115
130
  await vi.waitFor(() => expect(closes).toHaveLength(1));
@@ -146,8 +161,7 @@ describe("SseTransport", () => {
146
161
  fetchMock
147
162
  .mockResolvedValueOnce(sseResponse(first.stream))
148
163
  .mockResolvedValueOnce(sseResponse(second.stream));
149
- // Mirror useMarimoKernelConnection's retry path: reconnect()
150
- // synchronously from inside the close handler.
164
+ // Reconnect synchronously from the close handler.
151
165
  transport.addEventListener("close", () => transport.reconnect());
152
166
 
153
167
  transport.reconnect();