@marimo-team/frontend 0.25.3-dev13 → 0.25.3-dev134

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 (565) hide show
  1. package/dist/assets/CellStatus-DC6FFQOg.js +1 -0
  2. package/dist/assets/{ConnectedDataExplorerComponent-TprnxZeJ.js → ConnectedDataExplorerComponent-BV1mVpaE.js} +1 -1
  3. package/dist/assets/{ErrorBoundary-CM13wrd8.js → ErrorBoundary-Ce-Ujlwj.js} +2 -2
  4. package/dist/assets/JsonOutput-CfEBzaqW.js +11 -0
  5. package/dist/assets/{LazyAnyLanguageCodeMirror-B4fCknq1.js → LazyAnyLanguageCodeMirror-DoxWX_Wp.js} +2 -2
  6. package/dist/assets/MarimoErrorOutput-Bh6o9zle.js +10 -0
  7. package/dist/assets/{RenderHTML-DX6i99Op.js → RenderHTML-COGZsXQV.js} +1 -1
  8. package/dist/assets/RunButton-DQ51plUv.js +1 -0
  9. package/dist/assets/__vite-browser-external-BynxqS4w.js +1 -0
  10. package/dist/assets/__vite-browser-external-DTOT9NDN.js +1 -0
  11. package/dist/assets/{add-cell-with-ai-BpD_790A.js → add-cell-with-ai-D7gtGUsg.js} +6 -6
  12. package/dist/assets/{add-connection-dialog-B1BXCjex.js → add-connection-dialog-CpMCc97S.js} +1 -1
  13. package/dist/assets/{agent-panel-CDFUc1uu.js → agent-panel-BSFYqJ9B.js} +3 -3
  14. package/dist/assets/ai-model-dropdown-D575Vid8.js +13 -0
  15. package/dist/assets/alert-dialog-8YiYLrfr.js +1 -0
  16. package/dist/assets/any-language-editor-BWmz0P_C.js +1 -0
  17. package/dist/assets/{app-config-button-AdV-QDD7.js → app-config-button-Cfcg_5Bz.js} +1 -1
  18. package/dist/assets/arrays-0R2reF2y.js +1 -0
  19. package/dist/assets/{blob-BG8yoQmx.js → blob-CszMu60N.js} +1 -1
  20. package/dist/assets/{button-COI-NGrx.js → button-Cl7haVNh.js} +1 -1
  21. package/dist/assets/{cache-panel-DTY1dqvi.js → cache-panel-DuBsA6oO.js} +1 -1
  22. package/dist/assets/{capabilities-BJvf-MD4.js → capabilities-DcB-Dd0T.js} +1 -1
  23. package/dist/assets/cell-editor-BLpOlLnM.js +42 -0
  24. package/dist/assets/cell-link-Cj0gGaMV.js +1 -0
  25. package/dist/assets/cells-SvbLY10y.js +220 -0
  26. package/dist/assets/{chat-display-DhgI2MS9.js → chat-display-D43_g3TX.js} +1 -1
  27. package/dist/assets/chat-panel-B_HAJMwx.js +4 -0
  28. package/dist/assets/chat-ui-BEcYCphu.js +4 -0
  29. package/dist/assets/circle-B0vOU-Dh.js +1 -0
  30. package/dist/assets/{code-visibility-BJImdpDi.js → code-visibility-D1Wf7-vD.js} +1 -1
  31. package/dist/assets/column-preview-CrzaJKk8.js +1 -0
  32. package/dist/assets/command-palette-Bm9yIVcD.js +1 -0
  33. package/dist/assets/{common-Sl3uthjg.js → common-BJyUxCzv.js} +1 -1
  34. package/dist/assets/{components-CskjK6xZ.js → components-9p_w2C6e.js} +1 -1
  35. package/dist/assets/components-B6gDDccL.js +1 -0
  36. package/dist/assets/{components-3cou6Mka.js → components-D1f_Cz4i.js} +1 -1
  37. package/dist/assets/config-7IjqNuVp.js +1 -0
  38. package/dist/assets/config-BC32AAdw.js +1 -0
  39. package/dist/assets/{config-CA1V-wjh.js → config-CXnC3MRv.js} +1 -1
  40. package/dist/assets/{copy-D8O_CYCG.js → copy-74EjuZso.js} +1 -1
  41. package/dist/assets/{copy-icon-BIiRXh_3.js → copy-icon-Craaocbu.js} +1 -1
  42. package/dist/assets/{createReducer-DgRODTW6.js → createReducer-B04CIO-e.js} +1 -1
  43. package/dist/assets/{datasource-DfbNb83e.js → datasource-C8KSVfSD.js} +1 -1
  44. package/dist/assets/{dates-CfmdTL6B.js → dates-CaPZVGtm.js} +1 -1
  45. package/dist/assets/{dependency-graph-panel-L5cBE5sl.js → dependency-graph-panel-BhmyJbUl.js} +3 -3
  46. package/dist/assets/dist-gaK_BThd.js +1 -0
  47. package/dist/assets/{documentation-panel-D7S8JIc2.js → documentation-panel-DcVG0Q0d.js} +1 -1
  48. package/dist/assets/download-Bx52VRE4.js +9 -0
  49. package/dist/assets/dropdown-menu-Cwi17BoX.js +1 -0
  50. package/dist/assets/edit-page-DUrwOHR-.js +9 -0
  51. package/dist/assets/{error-banner-COZ7SfYV.js → error-banner-m3mastc-.js} +1 -1
  52. package/dist/assets/{error-entries-CLnPDv7b.js → error-entries-11OuHzUF.js} +1 -1
  53. package/dist/assets/{error-panel-D2xrYLue.js → error-panel-XJy5S25F.js} +1 -1
  54. package/dist/assets/{es-Cqst59WX.js → es-Rg27gAEy.js} +1 -1
  55. package/dist/assets/{feedback-button-DzPunIqU.js → feedback-button-B7jh_B4w.js} +1 -1
  56. package/dist/assets/feedback-button-C-eKtxXE.js +1 -0
  57. package/dist/assets/file-explorer-panel-CnVxbF4A.js +1 -0
  58. package/dist/assets/{file-name-input-B4fRaQkI.js → file-name-input-BJuqjQqH.js} +2 -2
  59. package/dist/assets/floating-outline-8yyZ-MOJ.js +1 -0
  60. package/dist/assets/{formats-yZ06h1Pl.js → formats-CYyvbiZA.js} +1 -1
  61. package/dist/assets/{formatting-BqrP-n49.js → formatting-DW2u7HXI.js} +1 -1
  62. package/dist/assets/fullscreen-D-ldV-19.js +41 -0
  63. package/dist/assets/{gallery-page-BefvdsM7.js → gallery-page-DJvGHePf.js} +1 -1
  64. package/dist/assets/{ganttDiagram-EL5Y4UJY-2n7Ig5Jm.js → ganttDiagram-EL5Y4UJY-CYqkCfFG.js} +1 -1
  65. package/dist/assets/{glide-data-editor-C3eaYlXg.js → glide-data-editor-Cyc8DHZx.js} +2 -2
  66. package/dist/assets/{glide-utils-CIrAxdjv.js → glide-utils-DsTeJ7WA.js} +2 -2
  67. package/dist/assets/home-page-tRZJmSU6.js +1 -0
  68. package/dist/assets/{hooks-DzML9nez.js → hooks-CxGwybGw.js} +1 -1
  69. package/dist/assets/{html-to-image-DK9Zl1lU.js → html-to-image-BW0fcUS8.js} +1 -1
  70. package/dist/assets/index-CoxbqhGH.js +30 -0
  71. package/dist/assets/index-u08m0hQE.css +2 -0
  72. package/dist/assets/{input-BoBxR50a.js → input-DLA5Dhn3.js} +2 -2
  73. package/dist/assets/kiosk-mode-Cbxffiz9.js +1 -0
  74. package/dist/assets/{layout-Ck1RoL1B.js → layout-DvXJxBiL.js} +4 -4
  75. package/dist/assets/linear-Bma8J3iM.js +1 -0
  76. package/dist/assets/links-UwsB2RA1.js +1 -0
  77. package/dist/assets/{logs-panel-BRytaWaO.js → logs-panel-KtsY7TrR.js} +1 -1
  78. package/dist/assets/{markdown-renderer-DX0Xrrfy.js → markdown-renderer-DUgV7_fm.js} +1 -1
  79. package/dist/assets/menu-items-yD7IV3V4.js +1 -0
  80. package/dist/assets/{mermaid-C4EiVqcd.js → mermaid-DmekSy8J.js} +2 -2
  81. package/dist/assets/{name-cell-input-DlpTvPLa.js → name-cell-input-C3-Cj-Ek.js} +1 -1
  82. package/dist/assets/numbers-DJK15_4S.js +1 -0
  83. package/dist/assets/{objects-snlMn4yk.js → objects-rLPlQ3X_.js} +1 -1
  84. package/dist/assets/{once-Haz_uxEL.js → once-6KNTRW0u.js} +1 -1
  85. package/dist/assets/{outline-panel-B2WIpz-1.js → outline-panel-BzyY4JA6.js} +1 -1
  86. package/dist/assets/{package-data-dv4FBHxR.js → package-data-CKPmBDtg.js} +1 -1
  87. package/dist/assets/packages-panel-DxosbWko.js +1 -0
  88. package/dist/assets/{panel-accordion-state-BqSH5ttk.js → panel-accordion-state-BRbTib2B.js} +1 -1
  89. package/dist/assets/panels-Da3071Q4.js +1 -0
  90. package/dist/assets/precisionRound-BxlUs8G4.js +1 -0
  91. package/dist/assets/process-output-CSwx7rvo.js +1 -0
  92. package/dist/assets/{quadrantDiagram-AXDQQJYC-yBhCDI7E.js → quadrantDiagram-AXDQQJYC-D9KH5Zge.js} +1 -1
  93. package/dist/assets/react-vega-BTNeboR2.js +189 -0
  94. package/dist/assets/{readonly-code-display-MnMEoYW5.js → readonly-code-display-B9y9423Y.js} +1 -1
  95. package/dist/assets/{readonly-python-code-ggs4hAzC.js → readonly-python-code-Dgw6trlk.js} +1 -1
  96. package/dist/assets/{renderShortcut-C1JwYcqW.js → renderShortcut-Bxv9ZLnD.js} +1 -1
  97. package/dist/assets/{requests-lt0JK8Jt.js → requests-BIFR-h4M.js} +1 -1
  98. package/dist/assets/requests-DuLJN9Tf.js +1 -0
  99. package/dist/assets/{reveal-component-PiV1eQk9.js → reveal-component-B9A4wkG-.js} +37 -37
  100. package/dist/assets/run-page-C1MQ5b_A.js +1 -0
  101. package/dist/assets/{runs-BLp5jezv.js → runs-C8uOPNAW.js} +1 -1
  102. package/dist/assets/{runtime-details-state-CoL-p0MC.js → runtime-details-state-D_hOQL9T.js} +1 -1
  103. package/dist/assets/{save-worker-BbUSwZRG.js → save-worker-UwdGcNK5.js} +2 -1
  104. package/dist/assets/scratchpad-panel-CBCsPmMW.js +1 -0
  105. package/dist/assets/{scroll-to-bottom-button-1FyowpWV.js → scroll-to-bottom-button-CidjyqiH.js} +1 -1
  106. package/dist/assets/secrets-panel-ConQpchl.js +1 -0
  107. package/dist/assets/select-CgWQvW1F.js +1 -0
  108. package/dist/assets/{session-DqoWmb7X.js → session-BHgGkjDm.js} +1 -1
  109. package/dist/assets/session-panel-CiHCKPlx.js +1 -0
  110. package/dist/assets/snippets-panel-C0VDE_4i.js +1 -0
  111. package/dist/assets/{spec-BfSBcO5B.js → spec-DFtwOyXT.js} +1 -1
  112. package/dist/assets/state-BYETGwDZ.js +1 -0
  113. package/dist/assets/{state-uwRvJLb8.js → state-CZu2E2Lb.js} +1 -1
  114. package/dist/assets/{state-C3oDoyFe.js → state-ClBFtZxn.js} +1 -1
  115. package/dist/assets/{state-DxQnC_1I.js → state-CrLrURYG.js} +1 -1
  116. package/dist/assets/{state-DtGB7xeJ.js → state-DrraMmqz.js} +1 -1
  117. package/dist/assets/state-DvU2AiFx.js +107 -0
  118. package/dist/assets/{strings-B2vv-ENv.js → strings-3lNX212T.js} +1 -1
  119. package/dist/assets/{swiper-component-a07domrZ.js → swiper-component-BwGv-Pu1.js} +1 -1
  120. package/dist/assets/switch-D2gdC_ep.js +1 -0
  121. package/dist/assets/tabs-BLczoY3k.js +1 -0
  122. package/dist/assets/{terminal-wnSHYT8N.js → terminal-DLgGyFIP.js} +1 -1
  123. package/dist/assets/{time-MgToa8VM.js → time-DswSXBDk.js} +1 -1
  124. package/dist/assets/toast-trUlkvx3.js +1 -0
  125. package/dist/assets/tooltip-B5lAA_jn.js +1 -0
  126. package/dist/assets/tooltip-BiQdpXA0.js +61 -0
  127. package/dist/assets/tracing-C5d51Nmi.js +1 -0
  128. package/dist/assets/tracing-panel-B89R8dWE.js +2 -0
  129. package/dist/assets/{tree-actions-DnUokmva.js → tree-actions-Cph8FVzh.js} +1 -1
  130. package/dist/assets/use-restore-focus-DMSzeuUr.js +1 -0
  131. package/dist/assets/{useAddCell-IN24thff.js → useAddCell-VPkB72pn.js} +1 -1
  132. package/dist/assets/useAsyncData-qvoPYHtx.js +1 -0
  133. package/dist/assets/useBoolean-BkeUcn1n.js +1 -0
  134. package/dist/assets/useCellActionButton--zI3GFnt.js +1 -0
  135. package/dist/assets/useDebounce-DwTQ_1Pu.js +1 -0
  136. package/dist/assets/{useDeepCompareMemoize-DFIVN9dn.js → useDeepCompareMemoize-B2DVK2De.js} +1 -1
  137. package/dist/assets/useDelayElapsed-BlsDT4TV.js +1 -0
  138. package/dist/assets/{useDeleteCell-fQlM46Y2.js → useDeleteCell-CjewKeSr.js} +1 -1
  139. package/dist/assets/{useDependencyPanelTab-ImXYINm_.js → useDependencyPanelTab-Bt_ka-wl.js} +1 -1
  140. package/dist/assets/{useEvent-Dfcbgjhg.js → useEvent-BDjgaBtz.js} +1 -1
  141. package/dist/assets/{useEventListener-D9cftcHj.js → useEventListener-DPOTkksd.js} +1 -1
  142. package/dist/assets/{useIframeCapabilities-CwTkczVN.js → useIframeCapabilities-sHeZt_EA.js} +1 -1
  143. package/dist/assets/{useInstallPackage-BW06eter.js → useInstallPackage-BfNKN33w.js} +1 -1
  144. package/dist/assets/{useInterval--x3s1VpI.js → useInterval-BcdwHHB5.js} +1 -1
  145. package/dist/assets/useNotebookActions-DV2S6KaG.js +1 -0
  146. package/dist/assets/{useRunCells-DSJPXE-j.js → useRunCells-BupCZpzk.js} +1 -1
  147. package/dist/assets/{useSplitCell-DQyMF5gd.js → useSplitCell-CaLIR0TS.js} +1 -1
  148. package/dist/assets/useStateForKey-CGMJQT8x.js +1 -0
  149. package/dist/assets/{useTheme-n8Dj1zH7.js → useTheme-C2dnh2vM.js} +1 -1
  150. package/dist/assets/{utils-CaQYvazl.js → utils-DfElfBpz.js} +1 -1
  151. package/dist/assets/vega-component-DJf6ho22.js +1 -0
  152. package/dist/assets/{vega-embed-container-DJPNkHPb.js → vega-embed-container-DtArSJz1.js} +1 -1
  153. package/dist/assets/vega-loader.browser-Bu25TKax.js +6 -0
  154. package/dist/assets/worker-jm2VC7pS.js +159 -0
  155. package/dist/assets/{write-secret-modal-DzKAInL2.js → write-secret-modal-CBi2vNK_.js} +1 -1
  156. package/dist/assets/{xychartDiagram-S5SC5T6Z-KbGzG6-u.js → xychartDiagram-S5SC5T6Z-CeRnBS5e.js} +1 -1
  157. package/dist/index.html +81 -79
  158. package/package.json +14 -14
  159. package/src/__tests__/host-imports.test.ts +1 -0
  160. package/src/__tests__/module-entry.test.ts +32 -0
  161. package/src/__tests__/mount.test.ts +0 -20
  162. package/src/components/ai/__tests__/ai-model-dropdown.test.tsx +63 -0
  163. package/src/components/ai/ai-model-dropdown.tsx +11 -6
  164. package/src/components/app-config/ai-config.tsx +7 -2
  165. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  166. package/src/components/chat/acp/__tests__/session-tabs.test.tsx +43 -0
  167. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  168. package/src/components/chat/acp/blocks.tsx +3 -2
  169. package/src/components/chat/acp/session-tabs.tsx +8 -3
  170. package/src/components/chat/chat-components.tsx +2 -0
  171. package/src/components/chat/chat-panel.tsx +9 -8
  172. package/src/components/chat/chat-utils.ts +4 -8
  173. package/src/components/data-table/TableTopBar.tsx +2 -0
  174. package/src/components/data-table/__tests__/chart-spec-model.test.ts +195 -195
  175. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  176. package/src/components/data-table/__tests__/columns.test.tsx +105 -0
  177. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  178. package/src/components/data-table/__tests__/data-table.test.tsx +1 -0
  179. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  180. package/src/components/data-table/__tests__/export-actions.test.tsx +105 -0
  181. package/src/components/data-table/__tests__/keyboard-selection.test.tsx +188 -0
  182. package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
  183. package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
  184. package/src/components/data-table/cell-selection/feature.ts +2 -2
  185. package/src/components/data-table/cell-selection/types.ts +2 -2
  186. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
  187. package/src/components/data-table/charts/__tests__/spec-snapshot.test.ts +20 -20
  188. package/src/components/data-table/charts/__tests__/spec.test.ts +78 -52
  189. package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
  190. package/src/components/data-table/charts/chart-spec/spec.ts +68 -43
  191. package/src/components/data-table/charts/charts.tsx +40 -30
  192. package/src/components/data-table/charts/storage.ts +16 -5
  193. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  194. package/src/components/data-table/column-summary/chart-spec-model.tsx +23 -16
  195. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  196. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  197. package/src/components/data-table/columns.tsx +45 -46
  198. package/src/components/data-table/context-menu.tsx +36 -20
  199. package/src/components/data-table/data-table.tsx +13 -1
  200. package/src/components/data-table/date-popover.tsx +6 -4
  201. package/src/components/data-table/export-actions.tsx +76 -48
  202. package/src/components/data-table/range-focus/atoms.ts +4 -6
  203. package/src/components/data-table/range-focus/use-cell-range-selection.ts +65 -38
  204. package/src/components/data-table/range-focus/use-scroll-into-view.ts +21 -2
  205. package/src/components/data-table/renderers.tsx +16 -7
  206. package/src/components/data-table/row-viewer-panel/__tests__/row-viewer.test.tsx +24 -1
  207. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +4 -4
  208. package/src/components/data-table/search-highlight.tsx +79 -0
  209. package/src/components/data-table/types.ts +1 -0
  210. package/src/components/data-table/url-detector.tsx +49 -9
  211. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  212. package/src/components/datasources/__tests__/column-preview.test.tsx +63 -2
  213. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  214. package/src/components/datasources/column-preview.tsx +8 -2
  215. package/src/components/datasources/datasources.tsx +48 -35
  216. package/src/components/debugger/debugger-code.tsx +5 -0
  217. package/src/components/dependency-graph/dependency-graph-tree.tsx +8 -8
  218. package/src/components/dependency-graph/elements.ts +24 -21
  219. package/src/components/dependency-graph/panels.tsx +2 -1
  220. package/src/components/dependency-graph/utils/__tests__/layout.test.ts +18 -11
  221. package/src/components/editor/SortableCell.tsx +1 -3
  222. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  223. package/src/components/editor/__tests__/package-alert.test.tsx +22 -1
  224. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +24 -0
  225. package/src/components/editor/actions/pair-with-agent-modal.tsx +18 -15
  226. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  227. package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
  228. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  229. package/src/components/editor/cell/CreateCellButton.tsx +8 -10
  230. package/src/components/editor/cell/DeleteButton.tsx +1 -0
  231. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  232. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  233. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  234. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  235. package/src/components/editor/cell/collapse.tsx +5 -3
  236. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  237. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
  238. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  239. package/src/components/editor/chrome/panels/outline/__tests__/outline-keyboard.test.tsx +84 -0
  240. package/src/components/editor/chrome/panels/outline/floating-outline.tsx +39 -14
  241. package/src/components/editor/chrome/panels/outline/useActiveOutline.tsx +3 -2
  242. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  243. package/src/components/editor/chrome/panels/secrets-panel.tsx +1 -0
  244. package/src/components/editor/chrome/panels/snippets-panel.tsx +5 -1
  245. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +12 -18
  246. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +58 -1
  247. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  248. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
  249. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  250. package/src/components/editor/connections/components.tsx +1 -1
  251. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  252. package/src/components/editor/controls/__tests__/keyboard-shortcuts.test.tsx +61 -0
  253. package/src/components/editor/controls/command-palette.tsx +22 -10
  254. package/src/components/editor/controls/keyboard-shortcuts.tsx +14 -5
  255. package/src/components/editor/dynamic-favicon.tsx +21 -40
  256. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +12 -23
  257. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  258. package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +47 -0
  259. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  260. package/src/components/editor/file-tree/file-explorer.tsx +45 -8
  261. package/src/components/editor/file-tree/file-name-input.tsx +1 -0
  262. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  263. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  264. package/src/components/editor/header/status.tsx +28 -5
  265. package/src/components/editor/links/__tests__/cell-link.test.tsx +146 -0
  266. package/src/components/editor/links/cell-link.tsx +14 -5
  267. package/src/components/editor/navigation/focus-utils.ts +4 -4
  268. package/src/components/editor/notebook-banner.tsx +1 -0
  269. package/src/components/editor/notebook-cell.tsx +11 -2
  270. package/src/components/editor/output/MarimoTracebackOutput.tsx +19 -17
  271. package/src/components/editor/output/__tests__/traceback.test.tsx +27 -1
  272. package/src/components/editor/output/ansi-reduce.ts +33 -20
  273. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  274. package/src/components/editor/package-alert.tsx +14 -7
  275. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +12 -6
  276. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  277. package/src/components/home/components.tsx +17 -31
  278. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  279. package/src/components/icons/large-spinner.tsx +3 -14
  280. package/src/components/icons/spinner.tsx +1 -1
  281. package/src/components/mcp/mcp-status-indicator.tsx +1 -1
  282. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  283. package/src/components/pages/home-page.tsx +164 -91
  284. package/src/components/scratchpad/scratchpad.test.tsx +95 -0
  285. package/src/components/scratchpad/scratchpad.tsx +16 -3
  286. package/src/components/slides/reveal-component.tsx +4 -2
  287. package/src/components/slides/slide-cell-view.tsx +1 -1
  288. package/src/components/slides/slide-form.tsx +1 -0
  289. package/src/components/tracing/tracing-keyboard.test.tsx +85 -0
  290. package/src/components/tracing/tracing.tsx +45 -41
  291. package/src/components/ui/__tests__/draggable-popover.test.tsx +24 -0
  292. package/src/components/ui/alert-dialog.tsx +1 -1
  293. package/src/components/ui/draggable-popover.tsx +24 -2
  294. package/src/core/ai/context/providers/datasource.ts +6 -2
  295. package/src/core/ai/tools/base.ts +16 -7
  296. package/src/core/ai/tools/edit-notebook-tool.ts +47 -45
  297. package/src/core/ai/tools/registry.ts +13 -8
  298. package/src/core/ai/tools/sample-tool.ts +6 -7
  299. package/src/core/attachments/state.ts +5 -8
  300. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -0
  301. package/src/core/cells/__tests__/cell.test.ts +39 -0
  302. package/src/core/cells/__tests__/cells.test.ts +3 -1
  303. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
  304. package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
  305. package/src/core/cells/__tests__/session.test.ts +117 -36
  306. package/src/core/cells/cell.ts +14 -3
  307. package/src/core/cells/cells.ts +3 -1
  308. package/src/core/cells/pending-delete-service.ts +2 -6
  309. package/src/core/cells/session.ts +1 -1
  310. package/src/core/cells/types.ts +10 -1
  311. package/src/core/codemirror/__tests__/setup.test.ts +29 -29
  312. package/src/core/codemirror/cells/__tests__/extensions.test.ts +15 -0
  313. package/src/core/codemirror/cells/extensions.ts +7 -3
  314. package/src/core/codemirror/keymaps/vim.ts +2 -6
  315. package/src/core/codemirror/language/__tests__/sql.test.ts +22 -20
  316. package/src/core/codemirror/language/extension.ts +8 -8
  317. package/src/core/codemirror/language/languages/markdown.ts +8 -6
  318. package/src/core/codemirror/language/languages/python.ts +16 -10
  319. package/src/core/codemirror/language/languages/sql/sql.ts +9 -7
  320. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  321. package/src/core/codemirror/language/types.ts +7 -7
  322. package/src/core/codemirror/rtc/extension.ts +24 -12
  323. package/src/core/codemirror/rtc/loro/awareness.ts +34 -19
  324. package/src/core/codemirror/rtc/loro/sync.ts +1 -0
  325. package/src/core/codemirror/utils.ts +1 -0
  326. package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
  327. package/src/core/datasets/data-source-discovery.ts +4 -6
  328. package/src/core/document/__tests__/codemirror.test.ts +485 -0
  329. package/src/core/document/__tests__/draft.test.ts +322 -0
  330. package/src/core/document/__tests__/fixtures.ts +92 -0
  331. package/src/core/document/__tests__/hooks.test.tsx +386 -0
  332. package/src/core/document/__tests__/lifecycle-scenarios.test.ts +61 -0
  333. package/src/core/document/__tests__/model.test.ts +191 -0
  334. package/src/core/document/__tests__/protocol-fixtures.ts +38 -0
  335. package/src/core/document/__tests__/recovery-scenarios.test.ts +230 -0
  336. package/src/core/document/__tests__/scenario-fixture.ts +43 -0
  337. package/src/core/document/__tests__/scenarios.test.ts +205 -0
  338. package/src/core/document/__tests__/state.test.ts +438 -0
  339. package/src/core/document/__tests__/submission.test.ts +203 -0
  340. package/src/core/document/__tests__/test-host.test.ts +92 -0
  341. package/src/core/document/__tests__/test-host.ts +247 -0
  342. package/src/core/document/__tests__/test-notebook.ts +149 -0
  343. package/src/core/document/__tests__/user.ts +61 -0
  344. package/src/core/document/codemirror.ts +311 -0
  345. package/src/core/document/draft.ts +472 -0
  346. package/src/core/document/hooks.tsx +118 -0
  347. package/src/core/document/index.ts +15 -0
  348. package/src/core/document/model.ts +55 -0
  349. package/src/core/document/select.ts +41 -0
  350. package/src/core/document/state.ts +374 -0
  351. package/src/core/document/sync.ts +369 -0
  352. package/src/core/host/AGENTS.md +8 -2
  353. package/src/core/host/__tests__/attachments.test.ts +56 -0
  354. package/src/core/host/__tests__/document.test.ts +126 -0
  355. package/src/core/host/__tests__/fixtures.ts +71 -0
  356. package/src/core/host/__tests__/replica.test.ts +550 -0
  357. package/src/core/host/__tests__/static.test.ts +169 -0
  358. package/src/core/host/__tests__/wasm.test.ts +162 -0
  359. package/src/core/host/backend.ts +19 -0
  360. package/src/core/host/connection.ts +1 -40
  361. package/src/core/host/index.ts +14 -0
  362. package/src/core/host/replica.ts +286 -0
  363. package/src/core/host/resources/attachments.ts +22 -0
  364. package/src/core/host/resources/document.ts +101 -0
  365. package/src/core/host/static.ts +72 -0
  366. package/src/core/host/wasm.ts +102 -0
  367. package/src/core/hotkeys/actions.ts +2 -4
  368. package/src/core/islands/__tests__/bootstrap.test.ts +0 -26
  369. package/src/core/islands/bootstrap.ts +0 -1
  370. package/src/core/islands/components/__tests__/IslandControls.test.tsx +7 -5
  371. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  372. package/src/core/network/connection.ts +4 -2
  373. package/src/core/network/useConnectionNotice.ts +8 -2
  374. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  375. package/src/core/runtime/__tests__/runtime.test.ts +32 -58
  376. package/src/core/runtime/runtime.ts +38 -42
  377. package/src/core/runtime/types.ts +0 -2
  378. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  379. package/src/core/state/jotai.ts +1 -1
  380. package/src/core/static/__tests__/files.test.ts +38 -10
  381. package/src/core/static/files.ts +2 -0
  382. package/src/core/wasm/bridge.ts +38 -10
  383. package/src/core/wasm/store.ts +3 -3
  384. package/src/core/wasm/worker/bootstrap.ts +2 -1
  385. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  386. package/src/core/wasm/worker/types.ts +11 -0
  387. package/src/core/wasm/worker/worker.ts +48 -0
  388. package/src/core/websocket/__tests__/backend.test.ts +200 -0
  389. package/src/core/websocket/__tests__/connection-state.test.ts +0 -12
  390. package/src/core/websocket/__tests__/useNotebookReplica.hook.test.tsx +141 -0
  391. package/src/core/websocket/__tests__/useNotebookSession.hook.test.tsx +154 -428
  392. package/src/core/websocket/backend.ts +106 -0
  393. package/src/core/websocket/connection-state.ts +1 -11
  394. package/src/core/websocket/state.ts +7 -0
  395. package/src/core/websocket/transports/__tests__/notebook.test.ts +445 -0
  396. package/src/core/websocket/transports/notebook.ts +190 -0
  397. package/src/core/websocket/types.ts +2 -2
  398. package/src/core/websocket/useNotebookReplica.ts +69 -0
  399. package/src/core/websocket/useNotebookSession.tsx +10 -22
  400. package/src/css/globals.css +5 -0
  401. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  402. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  403. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  404. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  405. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  406. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  407. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  408. package/src/hooks/useAsyncData.ts +3 -3
  409. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  410. package/src/hooks/useDebounce.ts +6 -10
  411. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  412. package/src/hooks/useDelayElapsed.ts +14 -10
  413. package/src/hooks/useElapsedTime.ts +3 -3
  414. package/src/hooks/useEventListener.ts +1 -2
  415. package/src/hooks/useInputHistory.ts +4 -7
  416. package/src/hooks/useInternalStateWithSync.ts +6 -6
  417. package/src/hooks/useResizeObserver.ts +4 -7
  418. package/src/hooks/useScript.ts +67 -70
  419. package/src/hooks/useStateForKey.ts +93 -0
  420. package/src/mount.tsx +0 -4
  421. package/src/plugins/__tests__/plugin-object-schemas.test.ts +104 -0
  422. package/src/plugins/core/registerReactComponent.tsx +1 -1
  423. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  424. package/src/plugins/impl/DataEditorPlugin.tsx +1 -1
  425. package/src/plugins/impl/DataTablePlugin.tsx +29 -33
  426. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  427. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  428. package/src/plugins/impl/FormPlugin.tsx +1 -1
  429. package/src/plugins/impl/RangeSliderPlugin.tsx +0 -1
  430. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  431. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  432. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +117 -1
  433. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  434. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  435. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
  436. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  437. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  438. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  439. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  440. package/src/plugins/impl/chat/chat-ui.tsx +14 -11
  441. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  442. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  443. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  444. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  445. package/src/plugins/impl/data-explorer/components/column-summary.tsx +4 -2
  446. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  447. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -2
  448. package/src/plugins/impl/data-frames/__tests__/sidebar.test.tsx +160 -0
  449. package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +66 -66
  450. package/src/plugins/impl/data-frames/panel.tsx +43 -16
  451. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  452. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  453. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  454. package/src/plugins/impl/panel/utils.ts +17 -0
  455. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +2 -5
  456. package/src/plugins/impl/plotly/selection.ts +25 -19
  457. package/src/plugins/impl/vega/VegaPlugin.tsx +2 -3
  458. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  459. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  460. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  461. package/src/stories/button.stories.tsx +1 -0
  462. package/src/stories/editor.stories.tsx +24 -17
  463. package/src/stories/form-wrapper.stories.tsx +2 -2
  464. package/src/stories/select.stories.tsx +2 -2
  465. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  466. package/src/utils/__tests__/arrays.test.ts +35 -0
  467. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  468. package/src/utils/__tests__/numbers.test.ts +48 -1
  469. package/src/utils/__tests__/objects.test.ts +21 -0
  470. package/src/utils/arrays.ts +10 -0
  471. package/src/utils/blob.ts +1 -1
  472. package/src/utils/numbers.ts +28 -17
  473. package/src/utils/objects.ts +3 -7
  474. package/src/utils/once.ts +19 -19
  475. package/src/utils/tracer.ts +9 -5
  476. package/src/utils/zod-utils.ts +1 -3
  477. package/dist/assets/CellStatus-DVOHpdeJ.js +0 -1
  478. package/dist/assets/JsonOutput-Cx1iyibB.js +0 -11
  479. package/dist/assets/MarimoErrorOutput-s_5GOX8k.js +0 -10
  480. package/dist/assets/RunButton-BKoh0zYG.js +0 -1
  481. package/dist/assets/__vite-browser-external-CYO_P3S8.js +0 -1
  482. package/dist/assets/__vite-browser-external-FeDVGZU8.js +0 -1
  483. package/dist/assets/ai-model-dropdown-i1vfGeVi.js +0 -13
  484. package/dist/assets/alert-dialog-BaPGzeOK.js +0 -1
  485. package/dist/assets/any-language-editor-BdTWdMCI.js +0 -1
  486. package/dist/assets/arrays-BqUx5USx.js +0 -1
  487. package/dist/assets/cell-editor-BKJ06H5F.js +0 -21
  488. package/dist/assets/cell-link-8Auigi8P.js +0 -1
  489. package/dist/assets/cells-C6YGYGdi.js +0 -220
  490. package/dist/assets/chat-panel-Cau71Ygr.js +0 -4
  491. package/dist/assets/chat-ui-C5ql1Up-.js +0 -4
  492. package/dist/assets/circle-DXf-VWZG.js +0 -1
  493. package/dist/assets/circle-play-CYIb0qT6.js +0 -1
  494. package/dist/assets/column-preview-BZ_iNhtJ.js +0 -1
  495. package/dist/assets/command-palette-DDC3kBet.js +0 -1
  496. package/dist/assets/components-YBmtV0p6.js +0 -1
  497. package/dist/assets/config-DRIpOKqh.js +0 -1
  498. package/dist/assets/config-Dy28fDwn.js +0 -1
  499. package/dist/assets/dist-iaThVDkA.js +0 -1
  500. package/dist/assets/download-C03FOElq.js +0 -9
  501. package/dist/assets/dropdown-menu-C1rnl-0o.js +0 -1
  502. package/dist/assets/edit-page-D_Rg0gQO.js +0 -9
  503. package/dist/assets/feedback-button-Kla7i5RT.js +0 -1
  504. package/dist/assets/file-explorer-panel-Uk4_QIJQ.js +0 -1
  505. package/dist/assets/floating-outline-B5MaM1JS.js +0 -1
  506. package/dist/assets/fullscreen-B9tHcfpV.js +0 -41
  507. package/dist/assets/home-page-hmvKlc1w.js +0 -4
  508. package/dist/assets/index-6b4mf8Ai.css +0 -2
  509. package/dist/assets/index-ptK1gxmo.js +0 -30
  510. package/dist/assets/kiosk-mode-BZECfg9k.js +0 -1
  511. package/dist/assets/linear-0eij_aAG.js +0 -1
  512. package/dist/assets/link-NuFVR7G9.js +0 -1
  513. package/dist/assets/links-DkkL9DVa.js +0 -1
  514. package/dist/assets/menu-items-BrwnTgyK.js +0 -1
  515. package/dist/assets/numbers-Mk3-CcoU.js +0 -1
  516. package/dist/assets/packages-panel-CI5ff9b-.js +0 -1
  517. package/dist/assets/panels-BCcZ_yLK.js +0 -107
  518. package/dist/assets/precisionRound-Bq_nmf5u.js +0 -1
  519. package/dist/assets/process-output-f3C3PD0Z.js +0 -1
  520. package/dist/assets/react-vega-C3rYGtP6.js +0 -189
  521. package/dist/assets/requests-CgSY1X4a.js +0 -1
  522. package/dist/assets/run-page-AfZhtEh1.js +0 -1
  523. package/dist/assets/scratchpad-panel-D2rWg4k8.js +0 -1
  524. package/dist/assets/secrets-panel-D7KN4tVs.js +0 -1
  525. package/dist/assets/select-DTBn5eTb.js +0 -1
  526. package/dist/assets/session-panel-CnANhcTo.js +0 -1
  527. package/dist/assets/snippets-panel-ClGHtEKQ.js +0 -1
  528. package/dist/assets/state-BAJYRhmO.js +0 -1
  529. package/dist/assets/state-CbjBtkdd.js +0 -3
  530. package/dist/assets/switch-CTaiAjrz.js +0 -1
  531. package/dist/assets/tabs-BuQlZFbt.js +0 -1
  532. package/dist/assets/toast-BGzLGnvw.js +0 -1
  533. package/dist/assets/tooltip-C9TuY18o.js +0 -1
  534. package/dist/assets/tooltip-mT3CGest.js +0 -61
  535. package/dist/assets/tracing-Dz2rYOFL.js +0 -1
  536. package/dist/assets/tracing-panel-DqQY9V91.js +0 -2
  537. package/dist/assets/use-restore-focus-BxEGwIbk.js +0 -1
  538. package/dist/assets/useAsyncData-C7aJJz0Y.js +0 -1
  539. package/dist/assets/useBoolean-d0Tty94N.js +0 -1
  540. package/dist/assets/useCellActionButton-DmaFZ4oF.js +0 -1
  541. package/dist/assets/useDebounce-B_ccBZTp.js +0 -1
  542. package/dist/assets/useNotebookActions-DHt3s-ia.js +0 -1
  543. package/dist/assets/vega-component-IouMe13t.js +0 -1
  544. package/dist/assets/vega-loader.browser-CLRkNgm7.js +0 -6
  545. package/dist/assets/worker-DTQ7oH2A.js +0 -158
  546. package/dist/assets/ws-BXWY541V.js +0 -22
  547. package/src/core/host/__tests__/events.test.ts +0 -46
  548. package/src/core/host/__tests__/notebook-client.test.ts +0 -191
  549. package/src/core/host/__tests__/protocol.test.ts +0 -214
  550. package/src/core/host/events.ts +0 -60
  551. package/src/core/host/notebook-client.ts +0 -226
  552. package/src/core/host/protocol.ts +0 -1943
  553. package/src/core/state/__tests__/jotai.test.ts +0 -25
  554. package/src/core/websocket/__tests__/createConnectionTransport.test.ts +0 -49
  555. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +0 -140
  556. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  557. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -335
  558. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  559. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  560. package/src/core/websocket/transports/basic.ts +0 -102
  561. package/src/core/websocket/transports/create.ts +0 -31
  562. package/src/core/websocket/transports/sse.ts +0 -308
  563. package/src/core/websocket/transports/transport.ts +0 -50
  564. package/src/core/websocket/transports/ws.ts +0 -98
  565. package/src/core/websocket/useNotebookClient.ts +0 -80
@@ -21,8 +21,8 @@ import {
21
21
  vi,
22
22
  } from "vitest";
23
23
 
24
- vi.mock("../transports/create", () => ({
25
- createConnectionTransport: vi.fn(),
24
+ vi.mock("../backend", () => ({
25
+ createEditorBackend: vi.fn(),
26
26
  }));
27
27
 
28
28
  vi.mock("@/core/runtime/config", async () => {
@@ -36,21 +36,13 @@ vi.mock("@/core/runtime/config", async () => {
36
36
  });
37
37
 
38
38
  import { MockNotebook } from "@/__mocks__/notebook";
39
- import * as Toast from "@/components/ui/use-toast";
40
- import { Logger } from "@/utils/Logger";
41
39
  import { Deferred } from "@/utils/Deferred";
42
40
  import { cellId } from "@/__tests__/branded";
43
41
  import { alertAtom, getPackageAlert } from "@/core/alerts/state";
44
- import {
45
- type Attachment,
46
- attachmentsAtom,
47
- agentAttachmentsAtom,
48
- } from "@/core/attachments/state";
49
42
  import { notebookAtom } from "@/core/cells/cells";
50
43
  import { AppConfigSchema } from "@/core/config/config-schema";
51
44
  import { ConnectionNotice } from "@/components/editor/alerts/connection-notice";
52
45
  import { kernelStartupErrorAtom } from "@/core/errors/state";
53
- import type { RunEvent } from "@/core/host/protocol";
54
46
  import type { NotificationPayload } from "@/core/kernel/messages";
55
47
  import { useRuntimeManager } from "@/core/runtime/config";
56
48
  import { initialRunCompletedAtom } from "../../kernel/state";
@@ -58,54 +50,75 @@ import { connectionAtom, startupProgressAtom } from "../../network/connection";
58
50
  import { generateSessionId } from "../../kernel/session";
59
51
  import { RuntimeManager } from "../../runtime/runtime";
60
52
  import { WebSocketClosedReason, ConnectionState } from "../types";
61
- import type { IConnectionTransport } from "../transports/transport";
53
+ import { classifyCloseEvent } from "../connection-state";
54
+ import { createEditorBackend } from "../backend";
55
+ import { fakeBackend } from "../../host/__tests__/fixtures";
62
56
  import { useNotebookSession } from "../useNotebookSession";
63
- import { createConnectionTransport } from "../transports/create";
57
+ import { type NotebookTransport, StreamStopped } from "@marimo-team/client";
64
58
 
65
59
  const SESSION_ID = generateSessionId();
66
60
 
67
- interface MockTransport {
68
- readyState: 0 | 1 | 2 | 3;
69
- reconnect: Mock<IConnectionTransport["reconnect"]>;
70
- close: Mock<IConnectionTransport["close"]>;
71
- send: Mock<IConnectionTransport["send"]>;
72
- addEventListener: Mock<IConnectionTransport["addEventListener"]>;
73
- removeEventListener: Mock<IConnectionTransport["removeEventListener"]>;
61
+ /** One connection the fake transport opened. */
62
+ interface Attempt {
63
+ open(): void;
64
+ receive(message: unknown): void;
65
+ drop(): void;
66
+ stop(error: StreamStopped): void;
74
67
  }
75
68
 
76
- function makeTransport(
77
- readyState: 0 | 1 | 2 | 3 = WebSocket.CLOSED,
78
- ): MockTransport {
79
- return {
80
- readyState,
81
- reconnect: vi.fn(),
82
- close: vi.fn(),
83
- send: vi.fn(),
84
- addEventListener: vi.fn(),
85
- removeEventListener: vi.fn(),
86
- };
69
+ /** A transport the test drives, one connection at a time. */
70
+ function makeTransport() {
71
+ const attempts: Attempt[] = [];
72
+ const transport: NotebookTransport = ({ signal }, handlers) =>
73
+ new Promise((resolve, reject) => {
74
+ attempts.push({
75
+ open: handlers.open,
76
+ receive: handlers.message,
77
+ drop: resolve,
78
+ stop: reject,
79
+ });
80
+ signal.addEventListener("abort", () => resolve(), { once: true });
81
+ });
82
+ return { transport, attempts };
87
83
  }
88
84
 
89
- function getTransportHandlers() {
90
- const result = vi.mocked(createConnectionTransport).mock.results.at(-1);
91
- if (!result || result.type !== "return") {
92
- throw new Error("No connection transport was created");
93
- }
94
- const transport = result.value;
95
- function emit(
96
- event: "open" | "close" | "error" | "message",
97
- message: Event | CloseEvent | MessageEvent,
98
- ) {
99
- const callback = vi
100
- .mocked(transport.addEventListener)
101
- .mock.calls.find(([name]) => name === event)?.[1];
102
- callback?.(message);
103
- }
85
+ type FakeTransport = ReturnType<typeof makeTransport>;
86
+
87
+ /** Use a fake transport for the next session the hook follows. */
88
+ function withTransport(fake: FakeTransport = makeTransport()) {
89
+ vi.mocked(createEditorBackend).mockImplementation(({ checkHealth }) =>
90
+ fakeBackend({ transport: fake.transport, checkHealth }),
91
+ );
92
+ return fake;
93
+ }
94
+
95
+ /** Let the stream open its first connection. */
96
+ const started = () => act(() => Promise.resolve());
97
+
98
+ /** Drive the latest connection the fake transport opened. */
99
+ function connection(fake: FakeTransport) {
100
+ const current = () => {
101
+ const attempt = fake.attempts.at(-1);
102
+ if (attempt === undefined) {
103
+ throw new Error("No connection was opened");
104
+ }
105
+ return attempt;
106
+ };
104
107
  return {
105
- onError: (event: Event) => emit("error", event),
106
- onOpen: (event: Event) => emit("open", event),
107
- onClose: (event: CloseEvent) => emit("close", event),
108
- onMessage: (event: MessageEvent) => emit("message", event),
108
+ open: () => current().open(),
109
+ receive: (message: unknown) => current().receive(message),
110
+ /** Deliver a notification as the session routes frame it. */
111
+ send: (notification: unknown) =>
112
+ current().receive({ event: "marimo", data: { marimo: notification } }),
113
+ /** End the connection so the stream reconnects. */
114
+ drop: () => current().drop(),
115
+ /** Close the connection with a reason the editor treats as final. */
116
+ close: (reason: string) =>
117
+ current().stop(
118
+ new StreamStopped(reason, {
119
+ cause: classifyCloseEvent({ reason }).status,
120
+ }),
121
+ ),
109
122
  };
110
123
  }
111
124
 
@@ -147,12 +160,12 @@ function renderConnectionHook(store: ReturnType<typeof createStore>) {
147
160
  }
148
161
 
149
162
  describe("useNotebookSession.reconnect()", () => {
150
- let transport: MockTransport;
163
+ let fake: FakeTransport;
151
164
  let isHealthy: Mock<() => Promise<boolean>>;
152
165
  let store: ReturnType<typeof createStore>;
153
166
 
154
167
  beforeEach(() => {
155
- transport = makeTransport(WebSocket.CLOSED);
168
+ fake = withTransport();
156
169
  isHealthy = vi.fn<() => Promise<boolean>>().mockResolvedValue(true);
157
170
  store = createStore();
158
171
  store.set(connectionAtom, {
@@ -160,55 +173,53 @@ describe("useNotebookSession.reconnect()", () => {
160
173
  code: WebSocketClosedReason.KERNEL_DISCONNECTED,
161
174
  reason: "kernel not found",
162
175
  });
163
- vi.mocked(createConnectionTransport).mockReturnValue(transport);
164
176
  vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager(isHealthy));
165
177
  });
166
178
 
167
- function renderUseHook() {
168
- return renderConnectionHook(store);
179
+ async function renderStopped() {
180
+ const hook = renderConnectionHook(store);
181
+ await started();
182
+ await act(async () => connection(fake).close("MARIMO_SHUTDOWN"));
183
+ return hook;
169
184
  }
170
185
 
171
- it("is a no-op when the transport is already OPEN", async () => {
172
- transport.readyState = WebSocket.OPEN;
173
- const { result } = renderUseHook();
174
- await act(async () => {
175
- await result.current.reconnect();
176
- });
177
- expect(isHealthy).not.toHaveBeenCalled();
178
- expect(transport.reconnect).not.toHaveBeenCalled();
179
- });
180
-
181
- it("is a no-op when the transport is already CONNECTING", async () => {
182
- transport.readyState = WebSocket.CONNECTING;
183
- const { result } = renderUseHook();
184
- await act(async () => {
185
- await result.current.reconnect();
186
- });
187
- expect(isHealthy).not.toHaveBeenCalled();
188
- expect(transport.reconnect).not.toHaveBeenCalled();
189
- });
186
+ it.each(["connecting", "connected"])(
187
+ "is a no-op while the stream is %s",
188
+ async (state) => {
189
+ const { result } = renderConnectionHook(store);
190
+ await started();
191
+ if (state === "connected") {
192
+ act(() => connection(fake).open());
193
+ }
194
+ await act(async () => {
195
+ await result.current.reconnect();
196
+ });
197
+ expect(isHealthy).not.toHaveBeenCalled();
198
+ expect(fake.attempts).toHaveLength(1);
199
+ },
200
+ );
190
201
 
191
202
  it("probes /health and reconnects when the runtime is healthy", async () => {
192
203
  isHealthy.mockResolvedValue(true);
193
- const { result } = renderUseHook();
204
+ const { result } = await renderStopped();
194
205
  await act(async () => {
195
206
  await result.current.reconnect();
196
207
  });
197
208
  expect(isHealthy).toHaveBeenCalledOnce();
198
- expect(transport.reconnect).toHaveBeenCalledOnce();
209
+ expect(fake.attempts).toHaveLength(2);
199
210
  expect(store.get(connectionAtom)).toEqual({
200
211
  state: ConnectionState.CONNECTING,
201
212
  });
202
213
  });
203
214
 
204
- it("transitions to CLOSED and does not call ws.reconnect when the probe fails", async () => {
215
+ it("transitions to CLOSED and does not reconnect when the probe fails", async () => {
205
216
  isHealthy.mockResolvedValue(false);
206
- const { result } = renderUseHook();
217
+ const { result } = await renderStopped();
207
218
  await act(async () => {
208
219
  await result.current.reconnect();
209
220
  });
210
221
  expect(isHealthy).toHaveBeenCalledOnce();
211
- expect(transport.reconnect).not.toHaveBeenCalled();
222
+ expect(fake.attempts).toHaveLength(1);
212
223
  expect(store.get(connectionAtom)).toEqual({
213
224
  state: ConnectionState.CLOSED,
214
225
  code: WebSocketClosedReason.KERNEL_DISCONNECTED,
@@ -219,144 +230,30 @@ describe("useNotebookSession.reconnect()", () => {
219
230
 
220
231
  describe("useNotebookSession messages", () => {
221
232
  let store: ReturnType<typeof createStore>;
233
+ let fake: FakeTransport;
222
234
 
223
- beforeEach(() => {
235
+ beforeEach(async () => {
224
236
  store = createStore();
225
- vi.mocked(createConnectionTransport).mockClear();
226
- vi.mocked(createConnectionTransport).mockReturnValue(
227
- makeTransport(WebSocket.OPEN),
228
- );
237
+ fake = withTransport();
229
238
  vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
230
239
  renderConnectionHook(store);
240
+ await started();
231
241
  });
232
242
 
233
243
  function receive(payload: unknown) {
234
- const options = getTransportHandlers();
235
- act(() => {
236
- options.onMessage(
237
- new MessageEvent("message", { data: JSON.stringify(payload) }),
238
- );
239
- });
244
+ act(() => connection(fake).receive(payload));
240
245
  }
241
246
 
242
- it("replaces attachment snapshots, including the last detach", () => {
243
- const agent: Attachment = {
244
- id: "a1",
245
- kind: "agent",
246
- name: "Pi",
247
- since: "2026-10-14T00:00:00Z",
248
- notebook_id: "nb-test",
249
- identity: "pi-thread",
250
- };
251
- const client: Attachment = {
252
- id: "c1",
253
- kind: "client",
254
- name: null,
255
- since: "2026-10-14T00:00:01Z",
256
- notebook_id: "nb-test",
257
- };
258
- for (const attachments of [[agent, client], [client], []]) {
259
- receive({ data: { op: "attachments", attachments } });
260
- expect(store.get(attachmentsAtom)).toEqual(attachments);
261
- }
262
- });
263
-
264
- it.each(["{broken", "null"])(
265
- "reports a malformed legacy frame and continues applying notifications: %s",
266
- (data) => {
267
- const toast = vi.spyOn(Toast, "toast");
268
- try {
269
- receive({ data: { op: "reconnected" } });
270
- act(() =>
271
- getTransportHandlers().onMessage(
272
- new MessageEvent("message", { data }),
273
- ),
274
- );
275
- expect(toast).toHaveBeenCalledExactlyOnceWith({
276
- title: "Failed to handle message",
277
- description: expect.any(String),
278
- variant: "danger",
279
- });
280
- expect(store.get(connectionAtom)).toEqual({
281
- state: ConnectionState.OPEN,
282
- });
283
- receive({
284
- data: {
285
- op: "completed-run",
286
- run_id: "r1",
287
- outcome: "succeeded",
288
- error: null,
289
- },
290
- });
291
- expect(store.get(initialRunCompletedAtom)).toBe(true);
292
- } finally {
293
- toast.mockRestore();
294
- }
295
- },
296
- );
297
-
298
- it.each(["session", "host"])(
299
- "records completion of the initial run from the %s stream",
300
- (stream) => {
301
- const notification = {
302
- op: "completed-run",
303
- run_id: null,
304
- outcome: "succeeded",
305
- error: null,
306
- } satisfies NotificationPayload["data"];
307
- expect(store.get(initialRunCompletedAtom)).toBe(false);
308
- receive(
309
- stream === "host"
310
- ? { id: "42", event: "marimo", data: { marimo: notification } }
311
- : { op: notification.op, data: notification },
312
- );
313
- expect(store.get(initialRunCompletedAtom)).toBe(true);
314
- },
315
- );
316
-
317
- it("ignores other host events before handling the next notification", () => {
318
- const errors = vi.spyOn(Logger, "error").mockImplementation(vi.fn());
319
- try {
320
- receive({ id: "1", event: "ready", data: {} });
321
- receive({
322
- id: "2",
323
- event: "run",
324
- data: {
325
- run: {
326
- id: "r1",
327
- notebook_id: "n1",
328
- caused_by: null,
329
- status: "completed",
330
- outcome: "succeeded",
331
- started_at: "2026-10-19T12:00:00Z",
332
- completed_at: "2026-10-19T12:00:01Z",
333
- },
334
- },
335
- } satisfies RunEvent);
336
- receive({
337
- id: "3",
338
- event: "future-event",
339
- data: { marimo: { op: "completed-run", run_id: "r1" } },
340
- });
341
- receive({ id: "4", event: "another-event", data: null });
342
- expect(store.get(initialRunCompletedAtom)).toBe(false);
343
- receive({
344
- id: "5",
345
- event: "marimo",
346
- data: {
347
- marimo: {
348
- op: "completed-run",
349
- run_id: "r1",
350
- outcome: "succeeded",
351
- error: null,
352
- },
353
- },
354
- });
355
- expect(store.get(initialRunCompletedAtom)).toBe(true);
356
- expect(errors).not.toHaveBeenCalled();
357
- } finally {
358
- errors.mockRestore();
359
- }
247
+ it("records completion of the initial run", () => {
248
+ const notification = {
249
+ op: "completed-run",
250
+ run_id: null,
251
+ outcome: "succeeded",
252
+ error: null,
253
+ } satisfies NotificationPayload["data"];
254
+ expect(store.get(initialRunCompletedAtom)).toBe(false);
255
+ receive({ id: "42", event: "marimo", data: { marimo: notification } });
256
+ expect(store.get(initialRunCompletedAtom)).toBe(true);
360
257
  });
361
258
  });
362
259
 
@@ -365,190 +262,35 @@ it.each(["kernel-ready", "reconnected"])(
365
262
  async (op) => {
366
263
  const store = createStore();
367
264
  store.set(connectionAtom, { state: ConnectionState.CONNECTING });
368
- vi.mocked(createConnectionTransport).mockClear();
369
- vi.mocked(createConnectionTransport).mockReturnValue(
370
- makeTransport(WebSocket.OPEN),
371
- );
372
- vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
373
- renderConnectionHook(store);
374
- const options = getTransportHandlers();
375
-
376
- await act(async () => {
377
- await options.onOpen(new Event("open"));
378
- });
379
- expect(store.get(connectionAtom).state).toBe(ConnectionState.CONNECTING);
380
-
381
- act(() => {
382
- options.onMessage(
383
- new MessageEvent("message", {
384
- data: JSON.stringify({
385
- op,
386
- data: {
387
- op,
388
- cell_ids: [],
389
- codes: [],
390
- names: [],
391
- configs: [],
392
- layout: null,
393
- resumed: true,
394
- ui_values: {},
395
- last_executed_code: {},
396
- last_execution_time: {},
397
- app_config: { width: "normal" },
398
- kiosk: false,
399
- capabilities: { terminal: false },
400
- auto_instantiated: false,
401
- consumer_capabilities: { edit: true, interact: true },
402
- },
403
- }),
404
- }),
405
- );
406
- });
407
- expect(store.get(connectionAtom).state).toBe(ConnectionState.OPEN);
408
- },
409
- );
410
-
411
- describe("attachment snapshots across browser connections", () => {
412
- const agent: Attachment = {
413
- id: "a1",
414
- kind: "agent",
415
- name: "Pi",
416
- since: "2026-10-14T00:00:00Z",
417
- notebook_id: "nb-test",
418
- identity: "pi-thread",
419
- };
420
-
421
- function connectedSession() {
422
- const store = createStore();
423
- store.set(connectionAtom, { state: ConnectionState.OPEN });
424
- store.set(attachmentsAtom, [agent]);
425
- const transport = makeTransport(WebSocket.OPEN);
426
- vi.mocked(createConnectionTransport).mockReturnValue(transport);
265
+ const fake = withTransport();
427
266
  vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
428
267
  renderConnectionHook(store);
429
- const options = getTransportHandlers();
430
- const receive = (data: NotificationPayload["data"]) =>
431
- act(() => {
432
- options.onMessage(
433
- new MessageEvent("message", {
434
- data: JSON.stringify({ op: data.op, data }),
435
- }),
436
- );
437
- });
438
- return { store, transport, options, receive };
439
- }
440
-
441
- it.each([
442
- "MARIMO_NO_FILE_KEY",
443
- "MARIMO_NO_SESSION_ID",
444
- "MARIMO_NO_SESSION",
445
- "MARIMO_SHUTDOWN",
446
- "MARIMO_KERNEL_STARTUP_ERROR",
447
- "MARIMO_UNAUTHORIZED",
448
- "MARIMO_KIOSK_NOT_ALLOWED",
449
- "MARIMO_TRANSPORT_EXHAUSTED",
450
- ])("clears cached attachments on %s", (reason) => {
451
- const { store, transport, options } = connectedSession();
452
- act(() => options.onClose(new CloseEvent("close", { reason })));
453
- expect(store.get(attachmentsAtom)).toEqual([]);
454
- expect(store.get(agentAttachmentsAtom)).toEqual([]);
455
- expect(store.get(connectionAtom).state).toBe(ConnectionState.CLOSED);
456
- expect(transport.reconnect).not.toHaveBeenCalled();
457
- });
458
-
459
- it("clears the snapshot on transport error", () => {
460
- const { store, options } = connectedSession();
461
- act(() => options.onError(new Event("error")));
462
- expect(store.get(attachmentsAtom)).toEqual([]);
463
- expect(store.get(agentAttachmentsAtom)).toEqual([]);
464
- });
268
+ await started();
269
+ const conn = connection(fake);
465
270
 
466
- it("waits for a fresh snapshot after a transient reconnect", async () => {
467
- const { store, transport, options, receive } = connectedSession();
468
- act(() => options.onClose(new CloseEvent("close")));
469
- expect(store.get(attachmentsAtom)).toEqual([]);
271
+ await act(async () => conn.open());
470
272
  expect(store.get(connectionAtom).state).toBe(ConnectionState.CONNECTING);
471
- expect(transport.reconnect).toHaveBeenCalledOnce();
472
273
 
473
- await act(async () => options.onOpen(new Event("open")));
474
- receive({ op: "reconnected" });
475
- expect(store.get(connectionAtom).state).toBe(ConnectionState.OPEN);
476
- expect(store.get(agentAttachmentsAtom)).toEqual([]);
477
-
478
- const refreshed = { ...agent, id: "a2", name: "Codex" };
479
- receive({ op: "attachments", attachments: [refreshed] });
480
- expect(store.get(agentAttachmentsAtom)).toEqual([refreshed]);
481
- receive({ op: "attachments", attachments: [] });
482
- expect(store.get(agentAttachmentsAtom)).toEqual([]);
483
- });
484
-
485
- it("keeps a fresh snapshot hidden until the session is ready", async () => {
486
- const { store, options, receive } = connectedSession();
487
- act(() => options.onClose(new CloseEvent("close")));
488
- await act(async () => options.onOpen(new Event("open")));
489
- receive({ op: "attachments", attachments: [agent] });
490
- expect(store.get(attachmentsAtom)).toEqual([agent]);
491
- expect(store.get(agentAttachmentsAtom)).toEqual([]);
492
- receive({ op: "reconnected" });
493
- expect(store.get(agentAttachmentsAtom)).toEqual([agent]);
494
- });
495
-
496
- it("invalidates an old snapshot when a new transport opens", async () => {
497
- const { store, options } = connectedSession();
498
- await act(async () => options.onOpen(new Event("open")));
499
- expect(store.get(attachmentsAtom)).toEqual([]);
500
- expect(store.get(agentAttachmentsAtom)).toEqual([]);
501
- });
502
- });
503
-
504
- it.each([true, false])(
505
- "keeps attachments only when kernel-ready resumes the session: %s",
506
- (resumed) => {
507
- const store = createStore();
508
- const attachments: Attachment[] = [
509
- {
510
- id: "a1",
511
- kind: "agent",
512
- name: "Pi",
513
- since: "2026-10-14T00:00:00Z",
514
- notebook_id: "nb-test",
515
- identity: "pi-thread",
516
- },
517
- ];
518
- store.set(attachmentsAtom, attachments);
519
- vi.mocked(createConnectionTransport).mockReturnValue(
520
- makeTransport(WebSocket.OPEN),
521
- );
522
- vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
523
- renderConnectionHook(store);
524
- const options = getTransportHandlers();
525
274
  act(() =>
526
- options.onMessage(
527
- new MessageEvent("message", {
528
- data: JSON.stringify({
529
- op: "kernel-ready",
530
- data: {
531
- op: "kernel-ready",
532
- cell_ids: [],
533
- codes: [],
534
- names: [],
535
- configs: [],
536
- layout: null,
537
- resumed,
538
- ui_values: {},
539
- last_executed_code: {},
540
- last_execution_time: {},
541
- app_config: { width: "normal" },
542
- kiosk: false,
543
- capabilities: { terminal: false },
544
- auto_instantiated: true,
545
- consumer_capabilities: { edit: true, interact: true },
546
- },
547
- }),
548
- }),
549
- ),
275
+ conn.send({
276
+ op,
277
+ cell_ids: [],
278
+ codes: [],
279
+ names: [],
280
+ configs: [],
281
+ layout: null,
282
+ resumed: true,
283
+ ui_values: {},
284
+ last_executed_code: {},
285
+ last_execution_time: {},
286
+ app_config: { width: "normal" },
287
+ kiosk: false,
288
+ capabilities: { terminal: false },
289
+ auto_instantiated: false,
290
+ consumer_capabilities: { edit: true, interact: true },
291
+ }),
550
292
  );
551
- expect(store.get(attachmentsAtom)).toEqual(resumed ? attachments : []);
293
+ expect(store.get(connectionAtom).state).toBe(ConnectionState.OPEN);
552
294
  },
553
295
  );
554
296
 
@@ -556,11 +298,10 @@ describe("connection notice", () => {
556
298
  beforeEach(() => vi.useFakeTimers());
557
299
  afterEach(() => vi.useRealTimers());
558
300
 
559
- function renderNotice() {
301
+ async function renderNotice() {
560
302
  const store = createStore();
561
303
  store.set(connectionAtom, { state: ConnectionState.CONNECTING });
562
- const transport = makeTransport(WebSocket.OPEN);
563
- vi.mocked(createConnectionTransport).mockReturnValue(transport);
304
+ const fake = withTransport();
564
305
  vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
565
306
  const Connection = () => {
566
307
  const { reconnect } = useNotebookSession({
@@ -582,20 +323,17 @@ describe("connection notice", () => {
582
323
  </ErrorBoundary>
583
324
  </JotaiProvider>,
584
325
  );
585
- const options = getTransportHandlers();
326
+ await started();
327
+ const conn = connection(fake);
328
+ // Messages arrive on an open connection.
329
+ act(() => conn.open());
586
330
  const send = (data: NotificationPayload["data"]) =>
587
- act(() => {
588
- options.onMessage(
589
- new MessageEvent("message", {
590
- data: JSON.stringify({ op: data.op, data }),
591
- }),
592
- );
593
- });
594
- return { store, transport, options, send };
331
+ act(() => conn.send(data));
332
+ return { store, fake, conn, send };
595
333
  }
596
334
 
597
- it("keeps elapsed time across startup phases and leaves reconnection to the footer once cells are available", () => {
598
- const { store, options, send } = renderNotice();
335
+ it("keeps elapsed time across startup phases and leaves reconnection to the footer once cells are available", async () => {
336
+ const { store, conn, send } = await renderNotice();
599
337
  send({
600
338
  op: "startup-progress",
601
339
  phase: "preparing-environment",
@@ -635,7 +373,7 @@ describe("connection notice", () => {
635
373
  expect(
636
374
  screen.queryByRole("region", { name: "Notebook startup" }),
637
375
  ).not.toBeInTheDocument();
638
- act(() => options.onClose(new CloseEvent("close")));
376
+ await act(async () => conn.drop());
639
377
  act(() => vi.advanceTimersByTime(500));
640
378
  expect(store.get(connectionAtom)).toEqual({
641
379
  state: ConnectionState.CONNECTING,
@@ -646,8 +384,8 @@ describe("connection notice", () => {
646
384
  ).not.toBeInTheDocument();
647
385
  });
648
386
 
649
- it("restores startup output before appending live chunks and resets it for a new attempt", () => {
650
- const { store, send } = renderNotice();
387
+ it("restores startup output before appending live chunks and resets it for a new attempt", async () => {
388
+ const { store, send } = await renderNotice();
651
389
  const snapshot = {
652
390
  op: "startup-progress",
653
391
  phase: "starting-kernel",
@@ -673,7 +411,7 @@ describe("connection notice", () => {
673
411
  });
674
412
 
675
413
  it("restores completed startup output without putting a connected kernel back into startup", async () => {
676
- const { store, options, send } = renderNotice();
414
+ const { store, conn, send } = await renderNotice();
677
415
  send({ op: "reconnected" });
678
416
  send({
679
417
  op: "startup-progress",
@@ -691,13 +429,15 @@ describe("connection notice", () => {
691
429
  screen.queryByRole("region", { name: "Notebook startup" }),
692
430
  ).not.toBeInTheDocument();
693
431
 
694
- // A new transport starts from the next session's authoritative snapshot.
695
- await act(async () => options.onOpen(new Event("open")));
432
+ // A new connection starts from the next session's authoritative snapshot.
433
+ await act(async () => conn.drop());
434
+ await act(async () => vi.advanceTimersByTime(2000));
435
+ await act(async () => conn.open());
696
436
  expect(store.get(startupProgressAtom)).toBeNull();
697
437
  });
698
438
 
699
- it("does not flash a notice when startup finishes within the delay", () => {
700
- const { send } = renderNotice();
439
+ it("does not flash a notice when startup finishes within the delay", async () => {
440
+ const { send } = await renderNotice();
701
441
  send({
702
442
  op: "startup-progress",
703
443
  phase: "preparing-environment",
@@ -725,16 +465,11 @@ describe("connection notice", () => {
725
465
  it.each(["preparing-environment", "starting-kernel"] as const)(
726
466
  "keeps a %s failure available until the user retries",
727
467
  async (phase) => {
728
- const { store, transport, options, send } = renderNotice();
468
+ const { store, fake, conn, send } = await renderNotice();
729
469
  send({ op: "startup-progress", phase, logs: "", log_mode: "replace" });
730
470
  const error = "A full diagnostic\nwith <stderr> details";
731
471
  send({ op: "kernel-startup-error", error });
732
- transport.readyState = WebSocket.CLOSED;
733
- act(() =>
734
- options.onClose(
735
- new CloseEvent("close", { reason: "MARIMO_KERNEL_STARTUP_ERROR" }),
736
- ),
737
- );
472
+ await act(async () => conn.close("MARIMO_KERNEL_STARTUP_ERROR"));
738
473
  expect(screen.getByRole("status")).toHaveTextContent(
739
474
  "Kernel failed to start",
740
475
  );
@@ -745,11 +480,11 @@ describe("connection notice", () => {
745
480
  expect(screen.getByLabelText("Error details")).toHaveTextContent(error, {
746
481
  normalizeWhitespace: false,
747
482
  });
748
- expect(transport.reconnect).not.toHaveBeenCalled();
483
+ expect(fake.attempts).toHaveLength(1);
749
484
  await act(async () =>
750
485
  fireEvent.click(screen.getByRole("button", { name: "Try again" })),
751
486
  );
752
- expect(transport.reconnect).toHaveBeenCalledOnce();
487
+ expect(fake.attempts).toHaveLength(2);
753
488
  expect(store.get(kernelStartupErrorAtom)).toBeNull();
754
489
  expect(
755
490
  screen.queryByRole("button", { name: "Try again" }),
@@ -769,23 +504,14 @@ describe("connection notice", () => {
769
504
  );
770
505
  });
771
506
 
772
- it("replaces environment state from snapshots, then continues live progress", () => {
507
+ it("replaces environment state from snapshots, then continues live progress", async () => {
773
508
  const store = createStore();
774
- vi.mocked(createConnectionTransport).mockClear();
775
- vi.mocked(createConnectionTransport).mockReturnValue(
776
- makeTransport(WebSocket.OPEN),
777
- );
509
+ const fake = withTransport();
778
510
  vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
779
511
  renderConnectionHook(store);
780
- const options = getTransportHandlers();
512
+ await started();
781
513
  function receive(data: NotificationPayload["data"]) {
782
- act(() =>
783
- options?.onMessage(
784
- new MessageEvent("message", {
785
- data: JSON.stringify({ op: data.op, data }),
786
- }),
787
- ),
788
- );
514
+ act(() => connection(fake).send(data));
789
515
  }
790
516
  receive({
791
517
  op: "environment-operation",