@marimo-team/frontend 0.25.2 → 0.25.3-dev100

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 (476) hide show
  1. package/dist/assets/CellStatus-fK4n2NPc.js +1 -0
  2. package/dist/assets/{ConnectedDataExplorerComponent-t8fH_TNc.js → ConnectedDataExplorerComponent-Dft8R6l5.js} +1 -1
  3. package/dist/assets/{ErrorBoundary-Gl5nCA8C.js → ErrorBoundary-CAEGdoFW.js} +2 -2
  4. package/dist/assets/JsonOutput-CyAKmZDD.js +11 -0
  5. package/dist/assets/{LazyAnyLanguageCodeMirror-CKcCbG2G.js → LazyAnyLanguageCodeMirror-BPYE76jR.js} +2 -2
  6. package/dist/assets/MarimoErrorOutput-CgZvP0p0.js +10 -0
  7. package/dist/assets/{RenderHTML-BbOqmHy3.js → RenderHTML-DQvxDDNS.js} +1 -1
  8. package/dist/assets/RunButton-BBDnDL6G.js +1 -0
  9. package/dist/assets/__vite-browser-external-8vDMR0Kk.js +1 -0
  10. package/dist/assets/__vite-browser-external-mKcnhO20.js +1 -0
  11. package/dist/assets/{add-cell-with-ai-8UBC0xwR.js → add-cell-with-ai-Clg5FELx.js} +12 -12
  12. package/dist/assets/{add-connection-dialog-DXQnd6eB.js → add-connection-dialog-BqdWmD-7.js} +2 -2
  13. package/dist/assets/{agent-panel-D7kMZLG-.js → agent-panel-5RxG5Y82.js} +5 -5
  14. package/dist/assets/ai-model-dropdown-Bvl1ktQq.js +13 -0
  15. package/dist/assets/{alert-dialog-BaPGzeOK.js → alert-dialog-C-sWmKva.js} +1 -1
  16. package/dist/assets/any-language-editor-DUMe1UCk.js +1 -0
  17. package/dist/assets/{app-config-button-BKEG4x9u.js → app-config-button-0QTeQ2Mv.js} +1 -1
  18. package/dist/assets/arrays-0R2reF2y.js +1 -0
  19. package/dist/assets/{blob-D5nfNHOo.js → blob-5VcB8K_t.js} +1 -1
  20. package/dist/assets/{button-COI-NGrx.js → button-D8gVKwe-.js} +1 -1
  21. package/dist/assets/{cache-panel-DTY1dqvi.js → cache-panel-BV-c7ed0.js} +1 -1
  22. package/dist/assets/{capabilities-BJvf-MD4.js → capabilities-Pq1YNPlK.js} +1 -1
  23. package/dist/assets/cell-editor-Byw5y-oI.js +42 -0
  24. package/dist/assets/cell-link-C5yYyzuF.js +1 -0
  25. package/dist/assets/{cells-BRkCaSsh.js → cells-D_11MjVB.js} +60 -60
  26. package/dist/assets/{chat-display-Dnagy4Nv.js → chat-display-BCR_ySiV.js} +1 -1
  27. package/dist/assets/chat-panel-RPRvBsaU.js +4 -0
  28. package/dist/assets/chat-ui-CGviVaRA.js +4 -0
  29. package/dist/assets/{circle-DXf-VWZG.js → circle-B6D_6D8w.js} +1 -1
  30. package/dist/assets/code-visibility-DYDagVy7.js +1 -0
  31. package/dist/assets/coerce-DWjFeXOY.js +1 -0
  32. package/dist/assets/column-preview-xe533-Cn.js +1 -0
  33. package/dist/assets/command-palette-BB9bWdWp.js +1 -0
  34. package/dist/assets/{common-Cu6CDbuw.js → common-CaeItbcI.js} +1 -1
  35. package/dist/assets/components-CBE8cOLK.js +1 -0
  36. package/dist/assets/{components-CP_UoFtT.js → components-DCo7mAWO.js} +1 -1
  37. package/dist/assets/{components-CGw16R_N.js → components-G6frbw0c.js} +1 -1
  38. package/dist/assets/config-CD1IuZFJ.js +1 -0
  39. package/dist/assets/config-DMz7oN0r.js +1 -0
  40. package/dist/assets/config-DpY86_Yq.js +1 -0
  41. package/dist/assets/{copy-D8O_CYCG.js → copy-KlhazlhB.js} +1 -1
  42. package/dist/assets/{copy-icon-BIiRXh_3.js → copy-icon-8-a5vovc.js} +1 -1
  43. package/dist/assets/{createReducer-DgRODTW6.js → createReducer-CaI_Oa0v.js} +1 -1
  44. package/dist/assets/{datasource-Cq6x_5ML.js → datasource-deuXnlU-.js} +1 -1
  45. package/dist/assets/{dates-BbFQ2oE4.js → dates-CQMwFko2.js} +1 -1
  46. package/dist/assets/{dependency-graph-panel--Kgtktcw.js → dependency-graph-panel-CIykLnJe.js} +2 -2
  47. package/dist/assets/{dist-iaThVDkA.js → dist-jlhMTKo_.js} +1 -1
  48. package/dist/assets/{documentation-panel-BHmbeznw.js → documentation-panel-2PQ8kQLa.js} +1 -1
  49. package/dist/assets/{download-DCJXy66H.js → download-dK52bLxZ.js} +1 -1
  50. package/dist/assets/{dropdown-menu-C1rnl-0o.js → dropdown-menu-DS6umAnH.js} +1 -1
  51. package/dist/assets/edit-page-DrElzw10.js +9 -0
  52. package/dist/assets/{error-banner-Cd69vvyX.js → error-banner-B5kzVf_0.js} +1 -1
  53. package/dist/assets/{error-entries-Dk3VZ-OK.js → error-entries-3GVk2KjS.js} +1 -1
  54. package/dist/assets/{error-panel-BPRYmC5J.js → error-panel-CzDRGPeC.js} +1 -1
  55. package/dist/assets/{errors-DCrsa1nH.js → errors-CNsgAw6m.js} +1 -1
  56. package/dist/assets/{es-Cqst59WX.js → es-N9SHpmG7.js} +1 -1
  57. package/dist/assets/{feedback-button-Bik2-e33.js → feedback-button-C5NgWpFg.js} +2 -2
  58. package/dist/assets/feedback-button-CONwLFpw.js +1 -0
  59. package/dist/assets/file-explorer-panel-B50xDnTZ.js +1 -0
  60. package/dist/assets/{file-name-input-Cdc-TVqO.js → file-name-input-DqwMSgGU.js} +1 -1
  61. package/dist/assets/{floating-outline-XD9FAV-f.js → floating-outline-ChmAuvxz.js} +1 -1
  62. package/dist/assets/{formats-yZ06h1Pl.js → formats-zylfwejq.js} +1 -1
  63. package/dist/assets/{formatting-BqrP-n49.js → formatting-uom1es4b.js} +1 -1
  64. package/dist/assets/{fullscreen-B9tHcfpV.js → fullscreen-BWTs8pYZ.js} +1 -1
  65. package/dist/assets/{gallery-page-Dm6mGwBi.js → gallery-page-CiL8aMS1.js} +1 -1
  66. package/dist/assets/{ganttDiagram-EL5Y4UJY-2n7Ig5Jm.js → ganttDiagram-EL5Y4UJY-CYqkCfFG.js} +1 -1
  67. package/dist/assets/{glide-data-editor-J9CAb70d.js → glide-data-editor-CqRclBkp.js} +2 -2
  68. package/dist/assets/{glide-utils-CIrAxdjv.js → glide-utils-HFqhvWHI.js} +2 -2
  69. package/dist/assets/home-page-BgZVl4dP.js +1 -0
  70. package/dist/assets/{hooks-93IwHd2u.js → hooks-DM5n7Osw.js} +1 -1
  71. package/dist/assets/{html-to-image-DK9Zl1lU.js → html-to-image-D_0rd3_B.js} +1 -1
  72. package/dist/assets/index-BnhSnwvB.css +2 -0
  73. package/dist/assets/index-EG2n8Oht.js +30 -0
  74. package/dist/assets/{input-BoBxR50a.js → input-DmHMEZGh.js} +1 -1
  75. package/dist/assets/kiosk-mode-DFS6HopF.js +1 -0
  76. package/dist/assets/layout-BpP8nTjb.js +9 -0
  77. package/dist/assets/linear-Bma8J3iM.js +1 -0
  78. package/dist/assets/links-DX2w97N9.js +1 -0
  79. package/dist/assets/{logs-panel-ABM16htT.js → logs-panel-B5eVAZtH.js} +1 -1
  80. package/dist/assets/{markdown-renderer-BG3sp2rs.js → markdown-renderer-BWUzqkJM.js} +1 -1
  81. package/dist/assets/{menu-items-BrwnTgyK.js → menu-items-BgUyNUpu.js} +1 -1
  82. package/dist/assets/{mermaid-a3xYymTy.js → mermaid-BKXlWk4F.js} +2 -2
  83. package/dist/assets/{name-cell-input-BgEBmqyL.js → name-cell-input-DXW68gRW.js} +1 -1
  84. package/dist/assets/numbers-CsZVw3Dc.js +1 -0
  85. package/dist/assets/{objects-snlMn4yk.js → objects-rLPlQ3X_.js} +1 -1
  86. package/dist/assets/{once-Haz_uxEL.js → once-6KNTRW0u.js} +1 -1
  87. package/dist/assets/{outline-panel-DCXQ3_F6.js → outline-panel-DPxJhJ_A.js} +1 -1
  88. package/dist/assets/{package-data-dv4FBHxR.js → package-data-CKPmBDtg.js} +1 -1
  89. package/dist/assets/packages-panel-gBE2FWQr.js +1 -0
  90. package/dist/assets/{panel-accordion-state-C1sdPp0m.js → panel-accordion-state-CCF5Gxnc.js} +1 -1
  91. package/dist/assets/panels-Dr6qMCvU.js +105 -0
  92. package/dist/assets/precisionRound-BxlUs8G4.js +1 -0
  93. package/dist/assets/process-output-Cw9IDcKQ.js +1 -0
  94. package/dist/assets/{quadrantDiagram-AXDQQJYC-yBhCDI7E.js → quadrantDiagram-AXDQQJYC-D9KH5Zge.js} +1 -1
  95. package/dist/assets/react-vega-BTNeboR2.js +189 -0
  96. package/dist/assets/{readonly-code-display-Do50PHI8.js → readonly-code-display-DYcZQ28T.js} +1 -1
  97. package/dist/assets/{readonly-python-code-DCSMIsTX.js → readonly-python-code-RFuQP50v.js} +1 -1
  98. package/dist/assets/{renderShortcut-BkVJQ_Zh.js → renderShortcut-rUwGqKgC.js} +1 -1
  99. package/dist/assets/{requests-lt0JK8Jt.js → requests-BIFR-h4M.js} +1 -1
  100. package/dist/assets/requests-DuLJN9Tf.js +1 -0
  101. package/dist/assets/{reveal-component-ltaepAqw.js → reveal-component-yRrDmCQz.js} +37 -37
  102. package/dist/assets/run-page-B69M4-zE.js +1 -0
  103. package/dist/assets/{runs-BLp5jezv.js → runs-C49hleBy.js} +1 -1
  104. package/dist/assets/{runtime-details-state-CSPobEdR.js → runtime-details-state-pobtu6TO.js} +1 -1
  105. package/dist/assets/{save-worker-BbUSwZRG.js → save-worker-pCkSHhuz.js} +1 -1
  106. package/dist/assets/{schemas-Dv42wLFq.js → schemas-ChRqMOTL.js} +1 -1
  107. package/dist/assets/scratchpad-panel-H44_pvb_.js +1 -0
  108. package/dist/assets/{scroll-to-bottom-button-1FyowpWV.js → scroll-to-bottom-button-CIkON2WJ.js} +1 -1
  109. package/dist/assets/{secrets-panel-Bctbtkrp.js → secrets-panel-bcfEKxRx.js} +1 -1
  110. package/dist/assets/{select-DTBn5eTb.js → select-3CVGlYrC.js} +1 -1
  111. package/dist/assets/{session-DqoWmb7X.js → session-CMLIzZKw.js} +1 -1
  112. package/dist/assets/session-panel-Bnd1iXkO.js +1 -0
  113. package/dist/assets/{snippets-panel-Bh1oTw4m.js → snippets-panel-DKApxKx9.js} +1 -1
  114. package/dist/assets/{spec-BfSBcO5B.js → spec-uCpVvaAR.js} +1 -1
  115. package/dist/assets/{state-uwRvJLb8.js → state-3mcyTS9q.js} +1 -1
  116. package/dist/assets/state-B_MCKzBL.js +1 -0
  117. package/dist/assets/state-Bl9BiY_s.js +3 -0
  118. package/dist/assets/{state-C3oDoyFe.js → state-C5WAeuTu.js} +1 -1
  119. package/dist/assets/{state-DxQnC_1I.js → state-Cv2JW3l9.js} +1 -1
  120. package/dist/assets/{state-XsPO8hNi.js → state-Dv059B3A.js} +1 -1
  121. package/dist/assets/{strings-B2vv-ENv.js → strings-C25fhMR_.js} +1 -1
  122. package/dist/assets/{swiper-component-a07domrZ.js → swiper-component-CRGRy5_O.js} +1 -1
  123. package/dist/assets/{switch-CTaiAjrz.js → switch-DYjBfpNJ.js} +1 -1
  124. package/dist/assets/{tabs-BuQlZFbt.js → tabs-CY8E4r9U.js} +1 -1
  125. package/dist/assets/{terminal-Ck_vK-34.js → terminal-Ca-kubKb.js} +1 -1
  126. package/dist/assets/{time-MgToa8VM.js → time-DswSXBDk.js} +1 -1
  127. package/dist/assets/{toast-BGzLGnvw.js → toast-Gzxi-le_.js} +1 -1
  128. package/dist/assets/tooltip-Brya4Ghn.js +61 -0
  129. package/dist/assets/{tooltip-C9TuY18o.js → tooltip-CkGm8X7m.js} +1 -1
  130. package/dist/assets/tracing-Clwy4SlW.js +1 -0
  131. package/dist/assets/tracing-panel-BwjLG_aC.js +2 -0
  132. package/dist/assets/{tree-actions-GM5E_bUs.js → tree-actions-BZNTQ12N.js} +1 -1
  133. package/dist/assets/{use-restore-focus-BxEGwIbk.js → use-restore-focus-DdS7Tl0F.js} +1 -1
  134. package/dist/assets/{useAddCell-GR7eEQrT.js → useAddCell-Cjuqk2YR.js} +1 -1
  135. package/dist/assets/useAsyncData-qvoPYHtx.js +1 -0
  136. package/dist/assets/useBoolean-Dd2nWXFJ.js +1 -0
  137. package/dist/assets/useCellActionButton-MoWLvuGe.js +1 -0
  138. package/dist/assets/useDebounce-DwTQ_1Pu.js +1 -0
  139. package/dist/assets/{useDeepCompareMemoize-DFIVN9dn.js → useDeepCompareMemoize-B2DVK2De.js} +1 -1
  140. package/dist/assets/useDelayElapsed-BlsDT4TV.js +1 -0
  141. package/dist/assets/{useDeleteCell-2_bMClHd.js → useDeleteCell-BC-MW7d1.js} +1 -1
  142. package/dist/assets/{useDependencyPanelTab-k3C3M18S.js → useDependencyPanelTab-CZU7i-B3.js} +1 -1
  143. package/dist/assets/{useEvent-Dfcbgjhg.js → useEvent-BDjgaBtz.js} +1 -1
  144. package/dist/assets/{useEventListener-D9cftcHj.js → useEventListener-CjDeLqFC.js} +1 -1
  145. package/dist/assets/{useIframeCapabilities-CwTkczVN.js → useIframeCapabilities-Czr9e4uG.js} +1 -1
  146. package/dist/assets/{useInstallPackage-iX1eufdl.js → useInstallPackage-wcDq9hYT.js} +1 -1
  147. package/dist/assets/{useInterval--x3s1VpI.js → useInterval-DJdQmXnq.js} +1 -1
  148. package/dist/assets/useNotebookActions-wFxVdRXQ.js +1 -0
  149. package/dist/assets/{useRunCells-B2vUhREg.js → useRunCells-ByaWxoIt.js} +1 -1
  150. package/dist/assets/{useSplitCell-DEb03htp.js → useSplitCell-YJIystwv.js} +1 -1
  151. package/dist/assets/useStateForKey-CGMJQT8x.js +1 -0
  152. package/dist/assets/{useTheme-COhSHkCj.js → useTheme-BAA27lh7.js} +1 -1
  153. package/dist/assets/{utils-CaQYvazl.js → utils-DfElfBpz.js} +1 -1
  154. package/dist/assets/vega-component-BeyxAJad.js +1 -0
  155. package/dist/assets/{vega-embed-container-DJPNkHPb.js → vega-embed-container-DtArSJz1.js} +1 -1
  156. package/dist/assets/vega-loader.browser-Bu25TKax.js +6 -0
  157. package/dist/assets/{worker-DTQ7oH2A.js → worker-bSCjCvwz.js} +1 -1
  158. package/dist/assets/{write-secret-modal-DzKAInL2.js → write-secret-modal-BEx-EUlh.js} +1 -1
  159. package/dist/assets/{xychartDiagram-S5SC5T6Z-KbGzG6-u.js → xychartDiagram-S5SC5T6Z-CeRnBS5e.js} +1 -1
  160. package/dist/index.html +80 -79
  161. package/package.json +8 -5
  162. package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
  163. package/src/__tests__/host-imports.test.ts +101 -0
  164. package/src/__tests__/mount.test.ts +8 -8
  165. package/src/components/app-config/ai-config.tsx +7 -2
  166. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  167. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  168. package/src/components/chat/acp/blocks.tsx +2 -2
  169. package/src/components/chat/chat-components.tsx +2 -0
  170. package/src/components/chat/chat-panel.tsx +9 -8
  171. package/src/components/chat/chat-utils.ts +4 -8
  172. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  173. package/src/components/data-table/__tests__/columns.test.tsx +105 -0
  174. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  175. package/src/components/data-table/__tests__/context-menu.test.ts +176 -0
  176. package/src/components/data-table/__tests__/date-filter-inputs.test.tsx +24 -1
  177. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  178. package/src/components/data-table/__tests__/export-actions.test.tsx +434 -6
  179. package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
  180. package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
  181. package/src/components/data-table/cell-selection/feature.ts +2 -2
  182. package/src/components/data-table/cell-selection/types.ts +2 -2
  183. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
  184. package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
  185. package/src/components/data-table/charts/charts.tsx +35 -25
  186. package/src/components/data-table/charts/storage.ts +16 -5
  187. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  188. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  189. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  190. package/src/components/data-table/columns.tsx +45 -46
  191. package/src/components/data-table/context-menu.tsx +161 -46
  192. package/src/components/data-table/data-table.tsx +7 -1
  193. package/src/components/data-table/date-filter-inputs.tsx +15 -42
  194. package/src/components/data-table/date-popover.tsx +6 -4
  195. package/src/components/data-table/export-actions.tsx +267 -163
  196. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +2 -2
  197. package/src/components/data-table/schemas.ts +14 -36
  198. package/src/components/data-table/search-highlight.tsx +79 -0
  199. package/src/components/data-table/types.ts +1 -0
  200. package/src/components/data-table/url-detector.tsx +49 -9
  201. package/src/components/data-table/utils.ts +18 -0
  202. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  203. package/src/components/datasources/__tests__/column-preview.test.tsx +63 -2
  204. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  205. package/src/components/datasources/column-preview.tsx +8 -2
  206. package/src/components/datasources/datasources.tsx +47 -35
  207. package/src/components/debugger/debugger-code.tsx +5 -0
  208. package/src/components/dependency-graph/panels.tsx +2 -1
  209. package/src/components/editor/Output.tsx +9 -2
  210. package/src/components/editor/SortableCell.tsx +1 -3
  211. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  212. package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +80 -252
  213. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +210 -34
  214. package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
  215. package/src/components/editor/actions/pair-with-agent-modal.tsx +100 -37
  216. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  217. package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
  218. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  219. package/src/components/editor/cell/CreateCellButton.tsx +8 -10
  220. package/src/components/editor/cell/DeleteButton.tsx +1 -0
  221. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  222. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  223. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  224. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  225. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  226. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
  227. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  228. package/src/components/editor/chrome/panels/error-panel.tsx +1 -0
  229. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  230. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +366 -0
  231. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +58 -1
  232. package/src/components/editor/chrome/wrapper/footer-item.tsx +32 -24
  233. package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +237 -0
  234. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  235. package/src/components/editor/chrome/wrapper/footer.tsx +2 -0
  236. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
  237. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  238. package/src/components/editor/connections/components.tsx +1 -1
  239. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  240. package/src/components/editor/controls/command-palette.tsx +22 -10
  241. package/src/components/editor/dynamic-favicon.tsx +21 -40
  242. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +537 -0
  243. package/src/components/editor/errors/send-error-report-button.tsx +69 -0
  244. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  245. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  246. package/src/components/editor/file-tree/file-explorer.tsx +10 -5
  247. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  248. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  249. package/src/components/editor/notebook-banner.tsx +1 -0
  250. package/src/components/editor/output/MarimoErrorOutput.tsx +21 -5
  251. package/src/components/editor/output/MarimoTracebackOutput.tsx +37 -22
  252. package/src/components/editor/output/__tests__/ansi-reduce.test.ts +18 -0
  253. package/src/components/editor/output/ansi-reduce.ts +12 -0
  254. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  255. package/src/components/editor/package-alert.tsx +6 -4
  256. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  257. package/src/components/home/components.tsx +17 -31
  258. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  259. package/src/components/icons/large-spinner.tsx +3 -14
  260. package/src/components/icons/spinner.tsx +1 -1
  261. package/src/components/mcp/mcp-status-indicator.tsx +1 -1
  262. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  263. package/src/components/pages/home-page.tsx +164 -91
  264. package/src/components/scratchpad/scratchpad.tsx +2 -2
  265. package/src/components/slides/reveal-component.tsx +4 -2
  266. package/src/components/slides/slide-cell-view.tsx +1 -1
  267. package/src/components/slides/slide-form.tsx +1 -0
  268. package/src/components/tracing/tracing.tsx +2 -2
  269. package/src/components/ui/alert-dialog.tsx +1 -1
  270. package/src/core/ai/context/providers/datasource.ts +6 -2
  271. package/src/core/attachments/__tests__/toast.test.tsx +58 -0
  272. package/src/core/attachments/state.ts +18 -0
  273. package/src/core/attachments/toast.ts +15 -0
  274. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -0
  275. package/src/core/cells/__tests__/cell.test.ts +39 -0
  276. package/src/core/cells/__tests__/cells.test.ts +55 -1
  277. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
  278. package/src/core/cells/__tests__/error-report.test.ts +139 -0
  279. package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
  280. package/src/core/cells/__tests__/session.test.ts +50 -1
  281. package/src/core/cells/cell.ts +14 -3
  282. package/src/core/cells/cells.ts +3 -1
  283. package/src/core/cells/error-report.ts +63 -0
  284. package/src/core/cells/pending-delete-service.ts +2 -6
  285. package/src/core/cells/session.ts +29 -2
  286. package/src/core/cells/types.ts +10 -1
  287. package/src/core/codemirror/__tests__/setup.test.ts +1 -1
  288. package/src/core/codemirror/keymaps/vim.ts +2 -6
  289. package/src/core/codemirror/language/languages/markdown.ts +1 -1
  290. package/src/core/codemirror/language/languages/python.ts +3 -3
  291. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  292. package/src/core/config/pair.ts +5 -3
  293. package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
  294. package/src/core/datasets/data-source-discovery.ts +4 -6
  295. package/src/core/document/__tests__/draft.test.ts +322 -0
  296. package/src/core/document/__tests__/fixtures.ts +92 -0
  297. package/src/core/document/__tests__/model.test.ts +483 -0
  298. package/src/core/document/__tests__/state.test.ts +446 -0
  299. package/src/core/document/__tests__/submission.test.ts +346 -0
  300. package/src/core/document/draft.ts +472 -0
  301. package/src/core/document/index.ts +10 -0
  302. package/src/core/document/model.ts +53 -0
  303. package/src/core/document/state.ts +401 -0
  304. package/src/core/document/sync.ts +359 -0
  305. package/src/core/edit-app.tsx +2 -2
  306. package/src/core/host/AGENTS.md +16 -0
  307. package/src/core/host/__tests__/attachments.test.ts +56 -0
  308. package/src/core/host/__tests__/document.test.ts +126 -0
  309. package/src/core/host/__tests__/fixtures.ts +41 -0
  310. package/src/core/host/__tests__/notebook-client.test.ts +570 -0
  311. package/src/core/host/connection.ts +42 -0
  312. package/src/core/host/notebook-client.ts +297 -0
  313. package/src/core/host/resources/attachments.ts +22 -0
  314. package/src/core/host/resources/document.ts +101 -0
  315. package/src/core/hotkeys/actions.ts +2 -4
  316. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  317. package/src/core/network/connection.ts +4 -2
  318. package/src/core/network/useConnectionNotice.ts +8 -2
  319. package/src/core/run-app.tsx +2 -2
  320. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  321. package/src/core/runtime/__tests__/runtime.test.ts +18 -17
  322. package/src/core/runtime/runtime.ts +26 -30
  323. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  324. package/src/core/state/jotai.ts +1 -1
  325. package/src/core/static/files.ts +2 -0
  326. package/src/core/wasm/bridge.ts +2 -10
  327. package/src/core/wasm/store.ts +3 -3
  328. package/src/core/wasm/worker/bootstrap.ts +1 -1
  329. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  330. package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -29
  331. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +223 -0
  332. package/src/core/websocket/__tests__/{useMarimoKernelConnection.hook.test.tsx → useNotebookSession.hook.test.tsx} +194 -184
  333. package/src/core/websocket/connection-state.ts +60 -0
  334. package/src/core/websocket/state.ts +7 -0
  335. package/src/core/websocket/transports/__tests__/notebook.test.ts +502 -0
  336. package/src/core/websocket/transports/notebook.ts +213 -0
  337. package/src/core/websocket/types.ts +9 -39
  338. package/src/core/websocket/useNotebookClient.ts +89 -0
  339. package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +53 -224
  340. package/src/css/globals.css +5 -0
  341. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  342. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  343. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  344. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  345. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  346. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  347. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  348. package/src/hooks/useAsyncData.ts +3 -3
  349. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  350. package/src/hooks/useDebounce.ts +5 -8
  351. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  352. package/src/hooks/useDelayElapsed.ts +14 -10
  353. package/src/hooks/useElapsedTime.ts +3 -3
  354. package/src/hooks/useInputHistory.ts +4 -7
  355. package/src/hooks/useInternalStateWithSync.ts +6 -6
  356. package/src/hooks/useResizeObserver.ts +4 -7
  357. package/src/hooks/useScript.ts +67 -70
  358. package/src/hooks/useStateForKey.ts +93 -0
  359. package/src/mount.tsx +3 -3
  360. package/src/plugins/core/registerReactComponent.tsx +1 -1
  361. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  362. package/src/plugins/impl/DataTablePlugin.tsx +14 -15
  363. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  364. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  365. package/src/plugins/impl/FormPlugin.tsx +1 -1
  366. package/src/plugins/impl/RangeSliderPlugin.tsx +0 -1
  367. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  368. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  369. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +201 -1
  370. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  371. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  372. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
  373. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  374. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  375. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  376. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  377. package/src/plugins/impl/chat/chat-ui.tsx +12 -10
  378. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  379. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  380. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  381. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  382. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  383. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +0 -1
  384. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  385. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  386. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  387. package/src/plugins/impl/panel/utils.ts +17 -0
  388. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  389. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  390. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  391. package/src/stories/button.stories.tsx +1 -0
  392. package/src/stories/editor.stories.tsx +24 -17
  393. package/src/stories/form-wrapper.stories.tsx +2 -2
  394. package/src/stories/select.stories.tsx +2 -2
  395. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  396. package/src/utils/__tests__/arrays.test.ts +35 -0
  397. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  398. package/src/utils/__tests__/dates.test.ts +116 -0
  399. package/src/utils/__tests__/numbers.test.ts +48 -1
  400. package/src/utils/__tests__/objects.test.ts +21 -0
  401. package/src/utils/arrays.ts +10 -0
  402. package/src/utils/blob.ts +1 -1
  403. package/src/utils/dates.ts +94 -0
  404. package/src/utils/numbers.ts +28 -17
  405. package/src/utils/objects.ts +3 -7
  406. package/src/utils/once.ts +19 -19
  407. package/src/utils/zod-utils.ts +1 -3
  408. package/dist/assets/CellStatus-BP2mavAq.js +0 -1
  409. package/dist/assets/JsonOutput-D6HIIsCb.js +0 -11
  410. package/dist/assets/MarimoErrorOutput-D60XkCny.js +0 -6
  411. package/dist/assets/RunButton-CK3LhnP5.js +0 -1
  412. package/dist/assets/__vite-browser-external-CYO_P3S8.js +0 -1
  413. package/dist/assets/__vite-browser-external-FeDVGZU8.js +0 -1
  414. package/dist/assets/ai-model-dropdown-B0z6-zhp.js +0 -5
  415. package/dist/assets/any-language-editor-BLCAD3yx.js +0 -1
  416. package/dist/assets/api-BeC_VSGv.js +0 -1
  417. package/dist/assets/arrays-BqUx5USx.js +0 -1
  418. package/dist/assets/cell-editor-ADVfycpN.js +0 -21
  419. package/dist/assets/cell-link-LSth_8hT.js +0 -1
  420. package/dist/assets/chat-panel-DQ6HkWEF.js +0 -4
  421. package/dist/assets/chat-ui-Df8Vcj6n.js +0 -4
  422. package/dist/assets/circle-play-CYIb0qT6.js +0 -1
  423. package/dist/assets/code-visibility-B-_GvwR5.js +0 -1
  424. package/dist/assets/coerce-CpE6kUdX.js +0 -1
  425. package/dist/assets/column-preview-Cm2D6vyX.js +0 -1
  426. package/dist/assets/command-palette-DTSQ7gOC.js +0 -1
  427. package/dist/assets/components-CXfO-F47.js +0 -1
  428. package/dist/assets/config-BOKzA3tF.js +0 -1
  429. package/dist/assets/config-DX8Y2WbJ.js +0 -1
  430. package/dist/assets/config-VPSq73n8.js +0 -1
  431. package/dist/assets/edit-page-ChcSgpke.js +0 -9
  432. package/dist/assets/feedback-button-B2gKC5cn.js +0 -1
  433. package/dist/assets/file-explorer-panel-BEEK5lcl.js +0 -1
  434. package/dist/assets/home-page-DMHnsRxm.js +0 -4
  435. package/dist/assets/index-Bcdzex-3.css +0 -2
  436. package/dist/assets/index-s-M1OXjs.js +0 -29
  437. package/dist/assets/kiosk-mode-BZECfg9k.js +0 -1
  438. package/dist/assets/layout-DmcNEu3P.js +0 -9
  439. package/dist/assets/linear-0eij_aAG.js +0 -1
  440. package/dist/assets/link-NuFVR7G9.js +0 -1
  441. package/dist/assets/links-DkkL9DVa.js +0 -1
  442. package/dist/assets/numbers-Mk3-CcoU.js +0 -1
  443. package/dist/assets/packages-panel-BMGOmZkR.js +0 -1
  444. package/dist/assets/pair-CaQyK6En.js +0 -1
  445. package/dist/assets/panels-CxVG8-v2.js +0 -4
  446. package/dist/assets/precisionRound-Bq_nmf5u.js +0 -1
  447. package/dist/assets/process-output-D0KGq04R.js +0 -1
  448. package/dist/assets/react-vega-C3rYGtP6.js +0 -189
  449. package/dist/assets/requests-CgSY1X4a.js +0 -1
  450. package/dist/assets/run-page-DF3c5cDk.js +0 -1
  451. package/dist/assets/scratchpad-panel-Dwd6W5PE.js +0 -1
  452. package/dist/assets/session-panel-VdDhEBa-.js +0 -1
  453. package/dist/assets/state-BAJYRhmO.js +0 -1
  454. package/dist/assets/state-_Qai_QyX.js +0 -3
  455. package/dist/assets/tooltip-mT3CGest.js +0 -61
  456. package/dist/assets/tracing-Di6pUmX8.js +0 -1
  457. package/dist/assets/tracing-panel-PeRXHD8_.js +0 -2
  458. package/dist/assets/useAsyncData-C7aJJz0Y.js +0 -1
  459. package/dist/assets/useBoolean-BDD6_xQQ.js +0 -1
  460. package/dist/assets/useCellActionButton-B-9eG5z_.js +0 -1
  461. package/dist/assets/useDebounce-B_ccBZTp.js +0 -1
  462. package/dist/assets/useNotebookActions-NHN6A-cN.js +0 -1
  463. package/dist/assets/vega-component-CnadTgoE.js +0 -1
  464. package/dist/assets/vega-loader.browser-CLRkNgm7.js +0 -6
  465. package/dist/assets/ws-BXWY541V.js +0 -22
  466. package/src/core/state/__tests__/jotai.test.ts +0 -25
  467. package/src/core/websocket/__tests__/useWebSocket.test.ts +0 -49
  468. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  469. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -338
  470. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  471. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  472. package/src/core/websocket/transports/basic.ts +0 -102
  473. package/src/core/websocket/transports/sse.ts +0 -308
  474. package/src/core/websocket/transports/transport.ts +0 -61
  475. package/src/core/websocket/transports/ws.ts +0 -98
  476. 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/notebook", () => ({
25
+ createNotebookTransport: 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,6 +36,7 @@ vi.mock("@/core/runtime/config", async () => {
41
36
  });
42
37
 
43
38
  import { MockNotebook } from "@/__mocks__/notebook";
39
+ import { Deferred } from "@/utils/Deferred";
44
40
  import { cellId } from "@/__tests__/branded";
45
41
  import { alertAtom, getPackageAlert } from "@/core/alerts/state";
46
42
  import { notebookAtom } from "@/core/cells/cells";
@@ -51,44 +47,96 @@ import type { NotificationPayload } from "@/core/kernel/messages";
51
47
  import { useRuntimeManager } from "@/core/runtime/config";
52
48
  import { initialRunCompletedAtom } from "../../kernel/state";
53
49
  import { connectionAtom, startupProgressAtom } from "../../network/connection";
54
- import type { SessionId } from "../../kernel/session";
50
+ import { generateSessionId } from "../../kernel/session";
51
+ import { RuntimeManager } from "../../runtime/runtime";
55
52
  import { WebSocketClosedReason, ConnectionState } from "../types";
56
- import type { IConnectionTransport } from "../transports/transport";
57
- import { useMarimoKernelConnection } from "../useMarimoKernelConnection";
58
- import { useConnectionTransport } from "../useWebSocket";
59
-
60
- interface MockTransport {
61
- readyState: 0 | 1 | 2 | 3;
62
- reconnect: Mock<IConnectionTransport["reconnect"]>;
63
- close: Mock<IConnectionTransport["close"]>;
64
- send: Mock<IConnectionTransport["send"]>;
65
- addEventListener: Mock<IConnectionTransport["addEventListener"]>;
66
- removeEventListener: Mock<IConnectionTransport["removeEventListener"]>;
53
+ import { classifyCloseEvent } from "../connection-state";
54
+ import { createNotebookTransport } from "../transports/notebook";
55
+ import { useNotebookSession } from "../useNotebookSession";
56
+ import { type NotebookTransport, StreamStopped } from "@marimo-team/client";
57
+
58
+ const SESSION_ID = generateSessionId();
59
+
60
+ /** One connection the fake transport opened. */
61
+ interface Attempt {
62
+ open(): void;
63
+ receive(message: unknown): void;
64
+ drop(): void;
65
+ stop(error: StreamStopped): void;
67
66
  }
68
67
 
69
- function makeTransport(
70
- readyState: 0 | 1 | 2 | 3 = WebSocket.CLOSED,
71
- ): MockTransport {
68
+ /** A transport the test drives, one connection at a time. */
69
+ function makeTransport() {
70
+ const attempts: Attempt[] = [];
71
+ const transport: NotebookTransport = ({ signal }, handlers) =>
72
+ new Promise((resolve, reject) => {
73
+ attempts.push({
74
+ open: handlers.open,
75
+ receive: handlers.message,
76
+ drop: resolve,
77
+ stop: reject,
78
+ });
79
+ signal.addEventListener("abort", () => resolve(), { once: true });
80
+ });
81
+ return { transport, attempts };
82
+ }
83
+
84
+ type FakeTransport = ReturnType<typeof makeTransport>;
85
+
86
+ /** Use a fake transport for the next session the hook follows. */
87
+ function withTransport(fake: FakeTransport = makeTransport()) {
88
+ vi.mocked(createNotebookTransport).mockReturnValue(fake.transport);
89
+ return fake;
90
+ }
91
+
92
+ /** Let the stream open its first connection. */
93
+ const started = () => act(() => Promise.resolve());
94
+
95
+ /** Drive the latest connection the fake transport opened. */
96
+ function connection(fake: FakeTransport) {
97
+ const current = () => {
98
+ const attempt = fake.attempts.at(-1);
99
+ if (attempt === undefined) {
100
+ throw new Error("No connection was opened");
101
+ }
102
+ return attempt;
103
+ };
72
104
  return {
73
- readyState,
74
- reconnect: vi.fn(),
75
- close: vi.fn(),
76
- send: vi.fn(),
77
- addEventListener: vi.fn(),
78
- removeEventListener: vi.fn(),
105
+ open: () => current().open(),
106
+ receive: (message: unknown) => current().receive(message),
107
+ /** Deliver a notification as the session routes frame it. */
108
+ send: (notification: unknown) =>
109
+ current().receive({ event: "marimo", data: { marimo: notification } }),
110
+ /** End the connection so the stream reconnects. */
111
+ drop: () => current().drop(),
112
+ /** Close the connection with a reason the editor treats as final. */
113
+ close: (reason: string) =>
114
+ current().stop(
115
+ new StreamStopped(reason, {
116
+ cause: classifyCloseEvent({ reason }).status,
117
+ }),
118
+ ),
79
119
  };
80
120
  }
81
121
 
82
122
  function makeRuntimeManager(
83
123
  reconcileFromHealth = vi.fn<() => Promise<boolean>>().mockResolvedValue(true),
84
124
  ) {
85
- return {
125
+ const runtime = new RuntimeManager(
126
+ { url: "http://localhost", lazy: true },
127
+ true,
128
+ );
129
+ vi.spyOn(runtime, "reconcileFromHealth").mockImplementation(
86
130
  reconcileFromHealth,
87
- probeHealth: vi.fn().mockResolvedValue(true),
88
- getWsURL: () => new URL("ws://localhost/ws"),
89
- waitForHealthy: vi.fn().mockResolvedValue(undefined),
90
- isSameOrigin: true,
91
- };
131
+ );
132
+ vi.spyOn(runtime, "probeHealth").mockResolvedValue(true);
133
+ vi.spyOn(runtime, "getWsURL").mockReturnValue(new URL("ws://localhost/ws"));
134
+ // Keep the initial connection pending; these tests exercise session retries.
135
+ vi.spyOn(runtime, "waitForHealthy").mockReturnValue(
136
+ new Deferred<void>().promise,
137
+ );
138
+ vi.spyOn(runtime, "isSameOrigin", "get").mockReturnValue(false);
139
+ return runtime;
92
140
  }
93
141
 
94
142
  function renderConnectionHook(store: ReturnType<typeof createStore>) {
@@ -99,22 +147,22 @@ function renderConnectionHook(store: ReturnType<typeof createStore>) {
99
147
  );
100
148
  return renderHook(
101
149
  () =>
102
- useMarimoKernelConnection({
103
- sessionId: "test-session" as SessionId,
150
+ useNotebookSession({
151
+ sessionId: SESSION_ID,
104
152
  autoInstantiate: false,
105
- setCells: () => {},
153
+ setCells: vi.fn(),
106
154
  }),
107
155
  { wrapper },
108
156
  );
109
157
  }
110
158
 
111
- describe("useMarimoKernelConnection.reconnect()", () => {
112
- let transport: MockTransport;
159
+ describe("useNotebookSession.reconnect()", () => {
160
+ let fake: FakeTransport;
113
161
  let isHealthy: Mock<() => Promise<boolean>>;
114
162
  let store: ReturnType<typeof createStore>;
115
163
 
116
164
  beforeEach(() => {
117
- transport = makeTransport(WebSocket.CLOSED);
165
+ fake = withTransport();
118
166
  isHealthy = vi.fn<() => Promise<boolean>>().mockResolvedValue(true);
119
167
  store = createStore();
120
168
  store.set(connectionAtom, {
@@ -122,59 +170,53 @@ describe("useMarimoKernelConnection.reconnect()", () => {
122
170
  code: WebSocketClosedReason.KERNEL_DISCONNECTED,
123
171
  reason: "kernel not found",
124
172
  });
125
- vi.mocked(useConnectionTransport).mockReturnValue(transport);
126
- vi.mocked(useRuntimeManager).mockReturnValue(
127
- makeRuntimeManager(isHealthy) as unknown as ReturnType<
128
- typeof useRuntimeManager
129
- >,
130
- );
173
+ vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager(isHealthy));
131
174
  });
132
175
 
133
- function renderUseHook() {
134
- return renderConnectionHook(store);
176
+ async function renderStopped() {
177
+ const hook = renderConnectionHook(store);
178
+ await started();
179
+ await act(async () => connection(fake).close("MARIMO_SHUTDOWN"));
180
+ return hook;
135
181
  }
136
182
 
137
- it("is a no-op when the transport is already OPEN", async () => {
138
- transport.readyState = WebSocket.OPEN;
139
- const { result } = renderUseHook();
140
- await act(async () => {
141
- await result.current.reconnect();
142
- });
143
- expect(isHealthy).not.toHaveBeenCalled();
144
- expect(transport.reconnect).not.toHaveBeenCalled();
145
- });
146
-
147
- it("is a no-op when the transport is already CONNECTING", async () => {
148
- transport.readyState = WebSocket.CONNECTING;
149
- const { result } = renderUseHook();
150
- await act(async () => {
151
- await result.current.reconnect();
152
- });
153
- expect(isHealthy).not.toHaveBeenCalled();
154
- expect(transport.reconnect).not.toHaveBeenCalled();
155
- });
183
+ it.each(["connecting", "connected"])(
184
+ "is a no-op while the stream is %s",
185
+ async (state) => {
186
+ const { result } = renderConnectionHook(store);
187
+ await started();
188
+ if (state === "connected") {
189
+ act(() => connection(fake).open());
190
+ }
191
+ await act(async () => {
192
+ await result.current.reconnect();
193
+ });
194
+ expect(isHealthy).not.toHaveBeenCalled();
195
+ expect(fake.attempts).toHaveLength(1);
196
+ },
197
+ );
156
198
 
157
199
  it("probes /health and reconnects when the runtime is healthy", async () => {
158
200
  isHealthy.mockResolvedValue(true);
159
- const { result } = renderUseHook();
201
+ const { result } = await renderStopped();
160
202
  await act(async () => {
161
203
  await result.current.reconnect();
162
204
  });
163
205
  expect(isHealthy).toHaveBeenCalledOnce();
164
- expect(transport.reconnect).toHaveBeenCalledOnce();
206
+ expect(fake.attempts).toHaveLength(2);
165
207
  expect(store.get(connectionAtom)).toEqual({
166
208
  state: ConnectionState.CONNECTING,
167
209
  });
168
210
  });
169
211
 
170
- it("transitions to CLOSED and does not call ws.reconnect when the probe fails", async () => {
212
+ it("transitions to CLOSED and does not reconnect when the probe fails", async () => {
171
213
  isHealthy.mockResolvedValue(false);
172
- const { result } = renderUseHook();
214
+ const { result } = await renderStopped();
173
215
  await act(async () => {
174
216
  await result.current.reconnect();
175
217
  });
176
218
  expect(isHealthy).toHaveBeenCalledOnce();
177
- expect(transport.reconnect).not.toHaveBeenCalled();
219
+ expect(fake.attempts).toHaveLength(1);
178
220
  expect(store.get(connectionAtom)).toEqual({
179
221
  state: ConnectionState.CLOSED,
180
222
  code: WebSocketClosedReason.KERNEL_DISCONNECTED,
@@ -183,30 +225,31 @@ describe("useMarimoKernelConnection.reconnect()", () => {
183
225
  });
184
226
  });
185
227
 
186
- describe("useMarimoKernelConnection messages", () => {
187
- it("records completion of the initial run", () => {
188
- const store = createStore();
189
- vi.mocked(useConnectionTransport).mockClear();
190
- vi.mocked(useConnectionTransport).mockReturnValue(
191
- makeTransport(WebSocket.OPEN),
192
- );
193
- vi.mocked(useRuntimeManager).mockReturnValue(
194
- makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
195
- );
228
+ describe("useNotebookSession messages", () => {
229
+ let store: ReturnType<typeof createStore>;
230
+ let fake: FakeTransport;
231
+
232
+ beforeEach(async () => {
233
+ store = createStore();
234
+ fake = withTransport();
235
+ vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
196
236
  renderConnectionHook(store);
237
+ await started();
238
+ });
197
239
 
198
- const options = vi.mocked(useConnectionTransport).mock.calls.at(-1)?.[0];
240
+ function receive(payload: unknown) {
241
+ act(() => connection(fake).receive(payload));
242
+ }
243
+
244
+ it("records completion of the initial run", () => {
245
+ const notification = {
246
+ op: "completed-run",
247
+ run_id: null,
248
+ outcome: "succeeded",
249
+ error: null,
250
+ } satisfies NotificationPayload["data"];
199
251
  expect(store.get(initialRunCompletedAtom)).toBe(false);
200
- act(() => {
201
- options?.onMessage(
202
- new MessageEvent("message", {
203
- data: JSON.stringify({
204
- op: "completed-run",
205
- data: { op: "completed-run", run_id: null },
206
- }),
207
- }),
208
- );
209
- });
252
+ receive({ id: "42", event: "marimo", data: { marimo: notification } });
210
253
  expect(store.get(initialRunCompletedAtom)).toBe(true);
211
254
  });
212
255
  });
@@ -216,47 +259,34 @@ it.each(["kernel-ready", "reconnected"])(
216
259
  async (op) => {
217
260
  const store = createStore();
218
261
  store.set(connectionAtom, { state: ConnectionState.CONNECTING });
219
- vi.mocked(useConnectionTransport).mockClear();
220
- vi.mocked(useConnectionTransport).mockReturnValue(
221
- makeTransport(WebSocket.OPEN),
222
- );
223
- vi.mocked(useRuntimeManager).mockReturnValue(
224
- makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
225
- );
262
+ const fake = withTransport();
263
+ vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
226
264
  renderConnectionHook(store);
227
- const options = vi.mocked(useConnectionTransport).mock.calls.at(-1)![0];
265
+ await started();
266
+ const conn = connection(fake);
228
267
 
229
- await act(async () => {
230
- await options.onOpen(new Event("open"));
231
- });
268
+ await act(async () => conn.open());
232
269
  expect(store.get(connectionAtom).state).toBe(ConnectionState.CONNECTING);
233
270
 
234
- act(() => {
235
- options.onMessage(
236
- new MessageEvent("message", {
237
- data: JSON.stringify({
238
- op,
239
- data: {
240
- op,
241
- cell_ids: [],
242
- codes: [],
243
- names: [],
244
- configs: [],
245
- layout: null,
246
- resumed: true,
247
- ui_values: {},
248
- last_executed_code: {},
249
- last_execution_time: {},
250
- app_config: { width: "normal" },
251
- kiosk: false,
252
- capabilities: { terminal: false },
253
- auto_instantiated: false,
254
- consumer_capabilities: { edit: true, interact: true },
255
- },
256
- }),
257
- }),
258
- );
259
- });
271
+ act(() =>
272
+ conn.send({
273
+ op,
274
+ cell_ids: [],
275
+ codes: [],
276
+ names: [],
277
+ configs: [],
278
+ layout: null,
279
+ resumed: true,
280
+ ui_values: {},
281
+ last_executed_code: {},
282
+ last_execution_time: {},
283
+ app_config: { width: "normal" },
284
+ kiosk: false,
285
+ capabilities: { terminal: false },
286
+ auto_instantiated: false,
287
+ consumer_capabilities: { edit: true, interact: true },
288
+ }),
289
+ );
260
290
  expect(store.get(connectionAtom).state).toBe(ConnectionState.OPEN);
261
291
  },
262
292
  );
@@ -265,19 +295,16 @@ describe("connection notice", () => {
265
295
  beforeEach(() => vi.useFakeTimers());
266
296
  afterEach(() => vi.useRealTimers());
267
297
 
268
- function renderNotice() {
298
+ async function renderNotice() {
269
299
  const store = createStore();
270
300
  store.set(connectionAtom, { state: ConnectionState.CONNECTING });
271
- const transport = makeTransport(WebSocket.OPEN);
272
- vi.mocked(useConnectionTransport).mockReturnValue(transport);
273
- vi.mocked(useRuntimeManager).mockReturnValue(
274
- makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
275
- );
301
+ const fake = withTransport();
302
+ vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
276
303
  const Connection = () => {
277
- const { reconnect } = useMarimoKernelConnection({
278
- sessionId: "test-session" as SessionId,
304
+ const { reconnect } = useNotebookSession({
305
+ sessionId: SESSION_ID,
279
306
  autoInstantiate: false,
280
- setCells: () => {},
307
+ setCells: vi.fn(),
281
308
  });
282
309
  return (
283
310
  <ConnectionNotice
@@ -293,20 +320,17 @@ describe("connection notice", () => {
293
320
  </ErrorBoundary>
294
321
  </JotaiProvider>,
295
322
  );
296
- const options = vi.mocked(useConnectionTransport).mock.calls.at(-1)![0];
323
+ await started();
324
+ const conn = connection(fake);
325
+ // Messages arrive on an open connection.
326
+ act(() => conn.open());
297
327
  const send = (data: NotificationPayload["data"]) =>
298
- act(() => {
299
- options.onMessage(
300
- new MessageEvent("message", {
301
- data: JSON.stringify({ op: data.op, data }),
302
- }),
303
- );
304
- });
305
- return { store, transport, options, send };
328
+ act(() => conn.send(data));
329
+ return { store, fake, conn, send };
306
330
  }
307
331
 
308
- it("keeps elapsed time across startup phases and leaves reconnection to the footer once cells are available", () => {
309
- const { store, options, send } = renderNotice();
332
+ it("keeps elapsed time across startup phases and leaves reconnection to the footer once cells are available", async () => {
333
+ const { store, conn, send } = await renderNotice();
310
334
  send({
311
335
  op: "startup-progress",
312
336
  phase: "preparing-environment",
@@ -346,7 +370,7 @@ describe("connection notice", () => {
346
370
  expect(
347
371
  screen.queryByRole("region", { name: "Notebook startup" }),
348
372
  ).not.toBeInTheDocument();
349
- act(() => options.onClose(new CloseEvent("close")));
373
+ await act(async () => conn.drop());
350
374
  act(() => vi.advanceTimersByTime(500));
351
375
  expect(store.get(connectionAtom)).toEqual({
352
376
  state: ConnectionState.CONNECTING,
@@ -357,8 +381,8 @@ describe("connection notice", () => {
357
381
  ).not.toBeInTheDocument();
358
382
  });
359
383
 
360
- it("restores startup output before appending live chunks and resets it for a new attempt", () => {
361
- const { store, send } = renderNotice();
384
+ it("restores startup output before appending live chunks and resets it for a new attempt", async () => {
385
+ const { store, send } = await renderNotice();
362
386
  const snapshot = {
363
387
  op: "startup-progress",
364
388
  phase: "starting-kernel",
@@ -384,7 +408,7 @@ describe("connection notice", () => {
384
408
  });
385
409
 
386
410
  it("restores completed startup output without putting a connected kernel back into startup", async () => {
387
- const { store, options, send } = renderNotice();
411
+ const { store, conn, send } = await renderNotice();
388
412
  send({ op: "reconnected" });
389
413
  send({
390
414
  op: "startup-progress",
@@ -402,13 +426,15 @@ describe("connection notice", () => {
402
426
  screen.queryByRole("region", { name: "Notebook startup" }),
403
427
  ).not.toBeInTheDocument();
404
428
 
405
- // A new transport starts from the next session's authoritative snapshot.
406
- await act(async () => options.onOpen(new Event("open")));
429
+ // A new connection starts from the next session's authoritative snapshot.
430
+ await act(async () => conn.drop());
431
+ await act(async () => vi.advanceTimersByTime(2000));
432
+ await act(async () => conn.open());
407
433
  expect(store.get(startupProgressAtom)).toBeNull();
408
434
  });
409
435
 
410
- it("does not flash a notice when startup finishes within the delay", () => {
411
- const { send } = renderNotice();
436
+ it("does not flash a notice when startup finishes within the delay", async () => {
437
+ const { send } = await renderNotice();
412
438
  send({
413
439
  op: "startup-progress",
414
440
  phase: "preparing-environment",
@@ -436,16 +462,11 @@ describe("connection notice", () => {
436
462
  it.each(["preparing-environment", "starting-kernel"] as const)(
437
463
  "keeps a %s failure available until the user retries",
438
464
  async (phase) => {
439
- const { store, transport, options, send } = renderNotice();
465
+ const { store, fake, conn, send } = await renderNotice();
440
466
  send({ op: "startup-progress", phase, logs: "", log_mode: "replace" });
441
467
  const error = "A full diagnostic\nwith <stderr> details";
442
468
  send({ op: "kernel-startup-error", error });
443
- transport.readyState = WebSocket.CLOSED;
444
- act(() =>
445
- options.onClose(
446
- new CloseEvent("close", { reason: "MARIMO_KERNEL_STARTUP_ERROR" }),
447
- ),
448
- );
469
+ await act(async () => conn.close("MARIMO_KERNEL_STARTUP_ERROR"));
449
470
  expect(screen.getByRole("status")).toHaveTextContent(
450
471
  "Kernel failed to start",
451
472
  );
@@ -456,11 +477,11 @@ describe("connection notice", () => {
456
477
  expect(screen.getByLabelText("Error details")).toHaveTextContent(error, {
457
478
  normalizeWhitespace: false,
458
479
  });
459
- expect(transport.reconnect).not.toHaveBeenCalled();
480
+ expect(fake.attempts).toHaveLength(1);
460
481
  await act(async () =>
461
482
  fireEvent.click(screen.getByRole("button", { name: "Try again" })),
462
483
  );
463
- expect(transport.reconnect).toHaveBeenCalledOnce();
484
+ expect(fake.attempts).toHaveLength(2);
464
485
  expect(store.get(kernelStartupErrorAtom)).toBeNull();
465
486
  expect(
466
487
  screen.queryByRole("button", { name: "Try again" }),
@@ -480,25 +501,14 @@ describe("connection notice", () => {
480
501
  );
481
502
  });
482
503
 
483
- it("replaces environment state from snapshots, then continues live progress", () => {
504
+ it("replaces environment state from snapshots, then continues live progress", async () => {
484
505
  const store = createStore();
485
- vi.mocked(useConnectionTransport).mockClear();
486
- vi.mocked(useConnectionTransport).mockReturnValue(
487
- makeTransport(WebSocket.OPEN),
488
- );
489
- vi.mocked(useRuntimeManager).mockReturnValue(
490
- makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
491
- );
506
+ const fake = withTransport();
507
+ vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
492
508
  renderConnectionHook(store);
493
- const options = vi.mocked(useConnectionTransport).mock.calls.at(-1)?.[0];
509
+ await started();
494
510
  function receive(data: NotificationPayload["data"]) {
495
- act(() =>
496
- options?.onMessage(
497
- new MessageEvent("message", {
498
- data: JSON.stringify({ op: data.op, data }),
499
- }),
500
- ),
501
- );
511
+ act(() => connection(fake).send(data));
502
512
  }
503
513
  receive({
504
514
  op: "environment-operation",
@@ -0,0 +1,60 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import type { CloseDecision } from "../host/connection";
4
+ import { Logger } from "@/utils/Logger";
5
+ import { ConnectionState, WebSocketClosedReason } from "./types";
6
+
7
+ // Keep server reasons in sync with ws_endpoint.py and ws/*.py.
8
+ // SSE carries the same reasons in close events. Unclassified reasons retry.
9
+ export function classifyCloseEvent(event: { reason?: string }): CloseDecision {
10
+ switch (event.reason) {
11
+ case "MARIMO_NO_FILE_KEY":
12
+ case "MARIMO_NO_SESSION_ID":
13
+ case "MARIMO_NO_SESSION":
14
+ case "MARIMO_SHUTDOWN":
15
+ return {
16
+ kind: "terminal",
17
+ status: {
18
+ state: ConnectionState.CLOSED,
19
+ code: WebSocketClosedReason.KERNEL_DISCONNECTED,
20
+ reason: "kernel not found",
21
+ },
22
+ };
23
+ case "MARIMO_UNAUTHORIZED":
24
+ return {
25
+ kind: "terminal",
26
+ status: {
27
+ state: ConnectionState.CLOSED,
28
+ code: WebSocketClosedReason.KERNEL_DISCONNECTED,
29
+ reason: "not authorized",
30
+ },
31
+ };
32
+ case "MARIMO_KIOSK_NOT_ALLOWED":
33
+ return {
34
+ kind: "terminal",
35
+ status: {
36
+ state: ConnectionState.CLOSED,
37
+ code: WebSocketClosedReason.KERNEL_DISCONNECTED,
38
+ reason: "kiosk mode is not available for this session",
39
+ },
40
+ };
41
+ case "MARIMO_KERNEL_STARTUP_ERROR":
42
+ return {
43
+ kind: "terminal",
44
+ status: {
45
+ state: ConnectionState.CLOSED,
46
+ code: WebSocketClosedReason.KERNEL_STARTUP_ERROR,
47
+ reason: "Failed to start kernel sandbox",
48
+ },
49
+ };
50
+ default:
51
+ if (event.reason) {
52
+ Logger.warn("Unrecognized connection close reason", event.reason);
53
+ }
54
+ }
55
+
56
+ return {
57
+ kind: "retry",
58
+ status: { state: ConnectionState.CONNECTING },
59
+ };
60
+ }
@@ -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);