@marimo-team/frontend 0.25.3-dev12 → 0.25.3-dev122

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 (500) hide show
  1. package/dist/assets/CellStatus-_Aw9_foI.js +1 -0
  2. package/dist/assets/{ConnectedDataExplorerComponent-TprnxZeJ.js → ConnectedDataExplorerComponent-BRgqbAsc.js} +1 -1
  3. package/dist/assets/{ErrorBoundary-CM13wrd8.js → ErrorBoundary-DxSzjvbx.js} +2 -2
  4. package/dist/assets/JsonOutput-B7pGj79O.js +11 -0
  5. package/dist/assets/{LazyAnyLanguageCodeMirror-B4fCknq1.js → LazyAnyLanguageCodeMirror-BPYE76jR.js} +2 -2
  6. package/dist/assets/MarimoErrorOutput-Bzo6Bkp5.js +10 -0
  7. package/dist/assets/{RenderHTML-DX6i99Op.js → RenderHTML-D6AC0IhC.js} +1 -1
  8. package/dist/assets/RunButton-BjYsPiKa.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-CFrR1iW4.js} +6 -6
  12. package/dist/assets/{add-connection-dialog-B1BXCjex.js → add-connection-dialog-CyNAB_W2.js} +1 -1
  13. package/dist/assets/{agent-panel-CDFUc1uu.js → agent-panel-VOrccb5q.js} +3 -3
  14. package/dist/assets/ai-model-dropdown-Bfxv_zvu.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-AdV-QDD7.js → app-config-button-BRcjv8A_.js} +1 -1
  18. package/dist/assets/arrays-0R2reF2y.js +1 -0
  19. package/dist/assets/{blob-BG8yoQmx.js → blob-DTYOX-m5.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-eKgYK8Bd.js +42 -0
  24. package/dist/assets/{cell-link-8Auigi8P.js → cell-link-D3rkqgbU.js} +1 -1
  25. package/dist/assets/{cells-C6YGYGdi.js → cells-2HETnlvE.js} +18 -18
  26. package/dist/assets/{chat-display-DhgI2MS9.js → chat-display-BrT4VJJK.js} +1 -1
  27. package/dist/assets/chat-panel-qpPNEwxV.js +4 -0
  28. package/dist/assets/chat-ui-SAdJxg-k.js +4 -0
  29. package/dist/assets/{circle-DXf-VWZG.js → circle-B6D_6D8w.js} +1 -1
  30. package/dist/assets/{code-visibility-BJImdpDi.js → code-visibility-DYDagVy7.js} +1 -1
  31. package/dist/assets/column-preview-yI7-INkZ.js +1 -0
  32. package/dist/assets/command-palette-DF-B_YpW.js +1 -0
  33. package/dist/assets/{common-Sl3uthjg.js → common-DgXKwblm.js} +1 -1
  34. package/dist/assets/components-CBE8cOLK.js +1 -0
  35. package/dist/assets/{components-3cou6Mka.js → components-CuMhY4Sf.js} +1 -1
  36. package/dist/assets/{components-CskjK6xZ.js → components-DCo7mAWO.js} +1 -1
  37. package/dist/assets/config-CD1IuZFJ.js +1 -0
  38. package/dist/assets/{config-DRIpOKqh.js → config-DMz7oN0r.js} +1 -1
  39. package/dist/assets/{config-CA1V-wjh.js → config-DpY86_Yq.js} +1 -1
  40. package/dist/assets/{copy-D8O_CYCG.js → copy-KlhazlhB.js} +1 -1
  41. package/dist/assets/{copy-icon-BIiRXh_3.js → copy-icon-8-a5vovc.js} +1 -1
  42. package/dist/assets/{createReducer-DgRODTW6.js → createReducer-CaI_Oa0v.js} +1 -1
  43. package/dist/assets/{datasource-DfbNb83e.js → datasource-D9JMcEAP.js} +1 -1
  44. package/dist/assets/{dates-CfmdTL6B.js → dates-CQMwFko2.js} +1 -1
  45. package/dist/assets/{dependency-graph-panel-L5cBE5sl.js → dependency-graph-panel-BVJWPCvw.js} +3 -3
  46. package/dist/assets/{dist-iaThVDkA.js → dist-jlhMTKo_.js} +1 -1
  47. package/dist/assets/{documentation-panel-D7S8JIc2.js → documentation-panel--wI4XHso.js} +1 -1
  48. package/dist/assets/{download-C03FOElq.js → download-Dod42wfD.js} +1 -1
  49. package/dist/assets/{dropdown-menu-C1rnl-0o.js → dropdown-menu-DS6umAnH.js} +1 -1
  50. package/dist/assets/edit-page-Uj9YSRMX.js +9 -0
  51. package/dist/assets/{error-banner-COZ7SfYV.js → error-banner-B5kzVf_0.js} +1 -1
  52. package/dist/assets/{error-entries-CLnPDv7b.js → error-entries-D00nWhWE.js} +1 -1
  53. package/dist/assets/{error-panel-D2xrYLue.js → error-panel-CcN_1PWf.js} +1 -1
  54. package/dist/assets/{es-Cqst59WX.js → es-N9SHpmG7.js} +1 -1
  55. package/dist/assets/{feedback-button-DzPunIqU.js → feedback-button-ByGgqUsX.js} +1 -1
  56. package/dist/assets/feedback-button-tbWwJ_t6.js +1 -0
  57. package/dist/assets/file-explorer-panel-eTEqtMPW.js +1 -0
  58. package/dist/assets/{file-name-input-B4fRaQkI.js → file-name-input-D5SfTNWh.js} +1 -1
  59. package/dist/assets/{floating-outline-B5MaM1JS.js → floating-outline-B-k3P_AO.js} +1 -1
  60. package/dist/assets/{formats-yZ06h1Pl.js → formats-zylfwejq.js} +1 -1
  61. package/dist/assets/{formatting-BqrP-n49.js → formatting-uom1es4b.js} +1 -1
  62. package/dist/assets/{fullscreen-B9tHcfpV.js → fullscreen-BWTs8pYZ.js} +1 -1
  63. package/dist/assets/{gallery-page-BefvdsM7.js → gallery-page-BzbMhDHi.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-BMqhUrHI.js} +2 -2
  66. package/dist/assets/{glide-utils-CIrAxdjv.js → glide-utils-HFqhvWHI.js} +2 -2
  67. package/dist/assets/home-page-DJcYWiga.js +1 -0
  68. package/dist/assets/{hooks-DzML9nez.js → hooks-CGLb_CCz.js} +1 -1
  69. package/dist/assets/{html-to-image-DK9Zl1lU.js → html-to-image-D_0rd3_B.js} +1 -1
  70. package/dist/assets/index-BnhSnwvB.css +2 -0
  71. package/dist/assets/index-bSIORW11.js +30 -0
  72. package/dist/assets/{input-BoBxR50a.js → input-DmHMEZGh.js} +1 -1
  73. package/dist/assets/kiosk-mode-DFS6HopF.js +1 -0
  74. package/dist/assets/{layout-Ck1RoL1B.js → layout-C2XnLAfO.js} +5 -5
  75. package/dist/assets/linear-Bma8J3iM.js +1 -0
  76. package/dist/assets/links-DX2w97N9.js +1 -0
  77. package/dist/assets/{logs-panel-BRytaWaO.js → logs-panel-B3T1GFBk.js} +1 -1
  78. package/dist/assets/{markdown-renderer-DX0Xrrfy.js → markdown-renderer-BhiTvxtL.js} +1 -1
  79. package/dist/assets/{menu-items-BrwnTgyK.js → menu-items-BgUyNUpu.js} +1 -1
  80. package/dist/assets/{mermaid-C4EiVqcd.js → mermaid-BKXlWk4F.js} +2 -2
  81. package/dist/assets/{name-cell-input-DlpTvPLa.js → name-cell-input-CgAObR6N.js} +1 -1
  82. package/dist/assets/numbers-CsZVw3Dc.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-BhBb7Vqw.js} +1 -1
  86. package/dist/assets/{package-data-dv4FBHxR.js → package-data-CKPmBDtg.js} +1 -1
  87. package/dist/assets/packages-panel-Bt0xNpFl.js +1 -0
  88. package/dist/assets/{panel-accordion-state-BqSH5ttk.js → panel-accordion-state-ZsUdpAYn.js} +1 -1
  89. package/dist/assets/panels-DJghvIKo.js +1 -0
  90. package/dist/assets/precisionRound-BxlUs8G4.js +1 -0
  91. package/dist/assets/process-output-CeOMwE17.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-TvOC7ESE.js} +1 -1
  95. package/dist/assets/{readonly-python-code-ggs4hAzC.js → readonly-python-code-Cqkfdeyo.js} +1 -1
  96. package/dist/assets/{renderShortcut-C1JwYcqW.js → renderShortcut-rUwGqKgC.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-CwE6U9oJ.js} +37 -37
  100. package/dist/assets/run-page-CtonWTtJ.js +1 -0
  101. package/dist/assets/{runs-BLp5jezv.js → runs-C49hleBy.js} +1 -1
  102. package/dist/assets/{runtime-details-state-CoL-p0MC.js → runtime-details-state-CKqJSfSR.js} +1 -1
  103. package/dist/assets/{save-worker-BbUSwZRG.js → save-worker-UwdGcNK5.js} +2 -1
  104. package/dist/assets/scratchpad-panel-CwVBedfZ.js +1 -0
  105. package/dist/assets/{scroll-to-bottom-button-1FyowpWV.js → scroll-to-bottom-button-CIkON2WJ.js} +1 -1
  106. package/dist/assets/{secrets-panel-D7KN4tVs.js → secrets-panel-bcfEKxRx.js} +1 -1
  107. package/dist/assets/{select-DTBn5eTb.js → select-3CVGlYrC.js} +1 -1
  108. package/dist/assets/{session-DqoWmb7X.js → session-CMLIzZKw.js} +1 -1
  109. package/dist/assets/session-panel-CdHdKUCF.js +1 -0
  110. package/dist/assets/{snippets-panel-ClGHtEKQ.js → snippets-panel-DYLWOLYw.js} +1 -1
  111. package/dist/assets/{spec-BfSBcO5B.js → spec-DlH4Mp7d.js} +1 -1
  112. package/dist/assets/{state-uwRvJLb8.js → state-3mcyTS9q.js} +1 -1
  113. package/dist/assets/state-B_MCKzBL.js +1 -0
  114. package/dist/assets/{state-C3oDoyFe.js → state-C5WAeuTu.js} +1 -1
  115. package/dist/assets/{state-DxQnC_1I.js → state-Cv2JW3l9.js} +1 -1
  116. package/dist/assets/{state-DtGB7xeJ.js → state-DZN9vZ6z.js} +1 -1
  117. package/dist/assets/state-DgECPVFs.js +107 -0
  118. package/dist/assets/{strings-B2vv-ENv.js → strings-C25fhMR_.js} +1 -1
  119. package/dist/assets/{swiper-component-a07domrZ.js → swiper-component-CRGRy5_O.js} +1 -1
  120. package/dist/assets/{switch-CTaiAjrz.js → switch-DYjBfpNJ.js} +1 -1
  121. package/dist/assets/{tabs-BuQlZFbt.js → tabs-CY8E4r9U.js} +1 -1
  122. package/dist/assets/{terminal-wnSHYT8N.js → terminal-Ca-kubKb.js} +1 -1
  123. package/dist/assets/{time-MgToa8VM.js → time-DswSXBDk.js} +1 -1
  124. package/dist/assets/{toast-BGzLGnvw.js → toast-Gzxi-le_.js} +1 -1
  125. package/dist/assets/tooltip-Brya4Ghn.js +61 -0
  126. package/dist/assets/{tooltip-C9TuY18o.js → tooltip-CkGm8X7m.js} +1 -1
  127. package/dist/assets/tracing-B0J2ctTm.js +1 -0
  128. package/dist/assets/tracing-panel-C1OCpoeE.js +2 -0
  129. package/dist/assets/{tree-actions-DnUokmva.js → tree-actions-sjBxIglc.js} +1 -1
  130. package/dist/assets/{use-restore-focus-BxEGwIbk.js → use-restore-focus-DdS7Tl0F.js} +1 -1
  131. package/dist/assets/{useAddCell-IN24thff.js → useAddCell-CaXnFGki.js} +1 -1
  132. package/dist/assets/useAsyncData-qvoPYHtx.js +1 -0
  133. package/dist/assets/useBoolean-Dd2nWXFJ.js +1 -0
  134. package/dist/assets/useCellActionButton-CJ90CMEj.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-D9N85822.js} +1 -1
  139. package/dist/assets/{useDependencyPanelTab-ImXYINm_.js → useDependencyPanelTab-CzAE0cm-.js} +1 -1
  140. package/dist/assets/{useEvent-Dfcbgjhg.js → useEvent-BDjgaBtz.js} +1 -1
  141. package/dist/assets/{useEventListener-D9cftcHj.js → useEventListener-CjDeLqFC.js} +1 -1
  142. package/dist/assets/{useIframeCapabilities-CwTkczVN.js → useIframeCapabilities-Czr9e4uG.js} +1 -1
  143. package/dist/assets/{useInstallPackage-BW06eter.js → useInstallPackage-wcDq9hYT.js} +1 -1
  144. package/dist/assets/{useInterval--x3s1VpI.js → useInterval-DJdQmXnq.js} +1 -1
  145. package/dist/assets/useNotebookActions-CAMEQ38y.js +1 -0
  146. package/dist/assets/{useRunCells-DSJPXE-j.js → useRunCells-DGakx8hj.js} +1 -1
  147. package/dist/assets/{useSplitCell-DQyMF5gd.js → useSplitCell-AK5BKPGM.js} +1 -1
  148. package/dist/assets/useStateForKey-CGMJQT8x.js +1 -0
  149. package/dist/assets/{useTheme-n8Dj1zH7.js → useTheme-BAA27lh7.js} +1 -1
  150. package/dist/assets/{utils-CaQYvazl.js → utils-DfElfBpz.js} +1 -1
  151. package/dist/assets/vega-component-BeyxAJad.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-BEx-EUlh.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 +12 -12
  159. package/src/__tests__/host-imports.test.ts +1 -0
  160. package/src/__tests__/mount.test.ts +0 -20
  161. package/src/components/app-config/ai-config.tsx +7 -2
  162. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  163. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  164. package/src/components/chat/acp/blocks.tsx +2 -2
  165. package/src/components/chat/chat-components.tsx +2 -0
  166. package/src/components/chat/chat-panel.tsx +9 -8
  167. package/src/components/chat/chat-utils.ts +4 -8
  168. package/src/components/data-table/__tests__/chart-spec-model.test.ts +195 -195
  169. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  170. package/src/components/data-table/__tests__/columns.test.tsx +105 -0
  171. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  172. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  173. package/src/components/data-table/__tests__/export-actions.test.tsx +105 -0
  174. package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
  175. package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
  176. package/src/components/data-table/cell-selection/feature.ts +2 -2
  177. package/src/components/data-table/cell-selection/types.ts +2 -2
  178. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
  179. package/src/components/data-table/charts/__tests__/spec-snapshot.test.ts +20 -20
  180. package/src/components/data-table/charts/__tests__/spec.test.ts +78 -52
  181. package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
  182. package/src/components/data-table/charts/chart-spec/spec.ts +68 -43
  183. package/src/components/data-table/charts/charts.tsx +40 -30
  184. package/src/components/data-table/charts/storage.ts +16 -5
  185. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  186. package/src/components/data-table/column-summary/chart-spec-model.tsx +23 -16
  187. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  188. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  189. package/src/components/data-table/columns.tsx +45 -46
  190. package/src/components/data-table/context-menu.tsx +36 -20
  191. package/src/components/data-table/data-table.tsx +7 -1
  192. package/src/components/data-table/date-popover.tsx +6 -4
  193. package/src/components/data-table/export-actions.tsx +76 -48
  194. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +2 -2
  195. package/src/components/data-table/search-highlight.tsx +79 -0
  196. package/src/components/data-table/types.ts +1 -0
  197. package/src/components/data-table/url-detector.tsx +49 -9
  198. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  199. package/src/components/datasources/__tests__/column-preview.test.tsx +63 -2
  200. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  201. package/src/components/datasources/column-preview.tsx +8 -2
  202. package/src/components/datasources/datasources.tsx +47 -35
  203. package/src/components/debugger/debugger-code.tsx +5 -0
  204. package/src/components/dependency-graph/dependency-graph-tree.tsx +8 -8
  205. package/src/components/dependency-graph/elements.ts +24 -21
  206. package/src/components/dependency-graph/panels.tsx +2 -1
  207. package/src/components/dependency-graph/utils/__tests__/layout.test.ts +18 -11
  208. package/src/components/editor/SortableCell.tsx +1 -3
  209. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  210. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +24 -0
  211. package/src/components/editor/actions/pair-with-agent-modal.tsx +18 -15
  212. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  213. package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
  214. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  215. package/src/components/editor/cell/CreateCellButton.tsx +8 -10
  216. package/src/components/editor/cell/DeleteButton.tsx +1 -0
  217. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  218. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  219. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  220. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  221. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  222. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
  223. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  224. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  225. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +12 -18
  226. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +58 -1
  227. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  228. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
  229. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  230. package/src/components/editor/connections/components.tsx +1 -1
  231. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  232. package/src/components/editor/controls/command-palette.tsx +22 -10
  233. package/src/components/editor/dynamic-favicon.tsx +21 -40
  234. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +12 -23
  235. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  236. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  237. package/src/components/editor/file-tree/file-explorer.tsx +10 -5
  238. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  239. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  240. package/src/components/editor/notebook-banner.tsx +1 -0
  241. package/src/components/editor/output/MarimoTracebackOutput.tsx +2 -2
  242. package/src/components/editor/output/ansi-reduce.ts +33 -20
  243. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  244. package/src/components/editor/package-alert.tsx +6 -4
  245. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +12 -6
  246. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  247. package/src/components/home/components.tsx +17 -31
  248. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  249. package/src/components/icons/large-spinner.tsx +3 -14
  250. package/src/components/icons/spinner.tsx +1 -1
  251. package/src/components/mcp/mcp-status-indicator.tsx +1 -1
  252. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  253. package/src/components/pages/home-page.tsx +164 -91
  254. package/src/components/scratchpad/scratchpad.tsx +2 -2
  255. package/src/components/slides/reveal-component.tsx +4 -2
  256. package/src/components/slides/slide-cell-view.tsx +1 -1
  257. package/src/components/slides/slide-form.tsx +1 -0
  258. package/src/components/tracing/tracing.tsx +2 -2
  259. package/src/components/ui/alert-dialog.tsx +1 -1
  260. package/src/core/ai/context/providers/datasource.ts +6 -2
  261. package/src/core/ai/tools/base.ts +16 -7
  262. package/src/core/ai/tools/edit-notebook-tool.ts +47 -45
  263. package/src/core/ai/tools/registry.ts +13 -8
  264. package/src/core/ai/tools/sample-tool.ts +6 -7
  265. package/src/core/attachments/state.ts +5 -8
  266. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -0
  267. package/src/core/cells/__tests__/cell.test.ts +39 -0
  268. package/src/core/cells/__tests__/cells.test.ts +3 -1
  269. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
  270. package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
  271. package/src/core/cells/__tests__/session.test.ts +117 -36
  272. package/src/core/cells/cell.ts +14 -3
  273. package/src/core/cells/cells.ts +3 -1
  274. package/src/core/cells/pending-delete-service.ts +2 -6
  275. package/src/core/cells/session.ts +1 -1
  276. package/src/core/cells/types.ts +10 -1
  277. package/src/core/codemirror/__tests__/setup.test.ts +29 -29
  278. package/src/core/codemirror/cells/__tests__/extensions.test.ts +15 -0
  279. package/src/core/codemirror/cells/extensions.ts +7 -3
  280. package/src/core/codemirror/keymaps/vim.ts +2 -6
  281. package/src/core/codemirror/language/__tests__/sql.test.ts +22 -20
  282. package/src/core/codemirror/language/extension.ts +8 -8
  283. package/src/core/codemirror/language/languages/markdown.ts +8 -6
  284. package/src/core/codemirror/language/languages/python.ts +16 -10
  285. package/src/core/codemirror/language/languages/sql/sql.ts +9 -7
  286. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  287. package/src/core/codemirror/language/types.ts +7 -7
  288. package/src/core/codemirror/rtc/extension.ts +24 -12
  289. package/src/core/codemirror/rtc/loro/awareness.ts +34 -19
  290. package/src/core/codemirror/rtc/loro/sync.ts +1 -0
  291. package/src/core/codemirror/utils.ts +1 -0
  292. package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
  293. package/src/core/datasets/data-source-discovery.ts +4 -6
  294. package/src/core/document/__tests__/codemirror.test.ts +483 -0
  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__/hooks.test.tsx +386 -0
  298. package/src/core/document/__tests__/lifecycle-scenarios.test.ts +61 -0
  299. package/src/core/document/__tests__/model.test.ts +158 -0
  300. package/src/core/document/__tests__/protocol-fixtures.ts +38 -0
  301. package/src/core/document/__tests__/recovery-scenarios.test.ts +230 -0
  302. package/src/core/document/__tests__/scenario-fixture.ts +42 -0
  303. package/src/core/document/__tests__/scenarios.test.ts +205 -0
  304. package/src/core/document/__tests__/state.test.ts +438 -0
  305. package/src/core/document/__tests__/submission.test.ts +203 -0
  306. package/src/core/document/__tests__/test-host.test.ts +92 -0
  307. package/src/core/document/__tests__/test-host.ts +247 -0
  308. package/src/core/document/__tests__/test-notebook.ts +139 -0
  309. package/src/core/document/__tests__/user.ts +61 -0
  310. package/src/core/document/codemirror.ts +311 -0
  311. package/src/core/document/draft.ts +472 -0
  312. package/src/core/document/hooks.tsx +118 -0
  313. package/src/core/document/index.ts +22 -0
  314. package/src/core/document/model.ts +52 -0
  315. package/src/core/document/select.ts +41 -0
  316. package/src/core/document/state.ts +374 -0
  317. package/src/core/document/sync.ts +363 -0
  318. package/src/core/host/AGENTS.md +12 -2
  319. package/src/core/host/__tests__/attachments.test.ts +56 -0
  320. package/src/core/host/__tests__/document.test.ts +126 -0
  321. package/src/core/host/__tests__/fixtures.ts +41 -0
  322. package/src/core/host/__tests__/notebook-client.test.ts +511 -132
  323. package/src/core/host/__tests__/static.test.ts +167 -0
  324. package/src/core/host/__tests__/wasm.test.ts +152 -0
  325. package/src/core/host/backend.ts +10 -0
  326. package/src/core/host/connection.ts +1 -40
  327. package/src/core/host/notebook-client.ts +204 -133
  328. package/src/core/host/resources/attachments.ts +22 -0
  329. package/src/core/host/resources/document.ts +101 -0
  330. package/src/core/host/static.ts +70 -0
  331. package/src/core/host/wasm.ts +92 -0
  332. package/src/core/hotkeys/actions.ts +2 -4
  333. package/src/core/islands/__tests__/bootstrap.test.ts +0 -26
  334. package/src/core/islands/bootstrap.ts +0 -1
  335. package/src/core/islands/components/__tests__/IslandControls.test.tsx +7 -5
  336. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  337. package/src/core/network/connection.ts +4 -2
  338. package/src/core/network/useConnectionNotice.ts +8 -2
  339. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  340. package/src/core/runtime/__tests__/runtime.test.ts +15 -58
  341. package/src/core/runtime/runtime.ts +25 -42
  342. package/src/core/runtime/types.ts +0 -2
  343. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  344. package/src/core/state/jotai.ts +1 -1
  345. package/src/core/static/__tests__/files.test.ts +38 -10
  346. package/src/core/static/files.ts +2 -0
  347. package/src/core/wasm/bridge.ts +38 -10
  348. package/src/core/wasm/store.ts +3 -3
  349. package/src/core/wasm/worker/bootstrap.ts +2 -1
  350. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  351. package/src/core/wasm/worker/types.ts +11 -0
  352. package/src/core/wasm/worker/worker.ts +48 -0
  353. package/src/core/websocket/__tests__/connection-state.test.ts +0 -12
  354. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +153 -70
  355. package/src/core/websocket/__tests__/useNotebookSession.hook.test.tsx +151 -428
  356. package/src/core/websocket/connection-state.ts +0 -10
  357. package/src/core/websocket/state.ts +7 -0
  358. package/src/core/websocket/transports/__tests__/notebook.test.ts +502 -0
  359. package/src/core/websocket/transports/notebook.ts +213 -0
  360. package/src/core/websocket/useNotebookClient.ts +38 -29
  361. package/src/core/websocket/useNotebookSession.tsx +1 -14
  362. package/src/css/globals.css +5 -0
  363. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  364. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  365. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  366. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  367. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  368. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  369. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  370. package/src/hooks/useAsyncData.ts +3 -3
  371. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  372. package/src/hooks/useDebounce.ts +6 -10
  373. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  374. package/src/hooks/useDelayElapsed.ts +14 -10
  375. package/src/hooks/useElapsedTime.ts +3 -3
  376. package/src/hooks/useEventListener.ts +1 -2
  377. package/src/hooks/useInputHistory.ts +4 -7
  378. package/src/hooks/useInternalStateWithSync.ts +6 -6
  379. package/src/hooks/useResizeObserver.ts +4 -7
  380. package/src/hooks/useScript.ts +67 -70
  381. package/src/hooks/useStateForKey.ts +93 -0
  382. package/src/mount.tsx +0 -4
  383. package/src/plugins/__tests__/plugin-object-schemas.test.ts +104 -0
  384. package/src/plugins/core/registerReactComponent.tsx +1 -1
  385. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  386. package/src/plugins/impl/DataEditorPlugin.tsx +1 -1
  387. package/src/plugins/impl/DataTablePlugin.tsx +29 -33
  388. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  389. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  390. package/src/plugins/impl/FormPlugin.tsx +1 -1
  391. package/src/plugins/impl/RangeSliderPlugin.tsx +0 -1
  392. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  393. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  394. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +117 -1
  395. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  396. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  397. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
  398. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  399. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  400. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  401. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  402. package/src/plugins/impl/chat/chat-ui.tsx +12 -10
  403. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  404. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  405. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  406. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  407. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  408. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -2
  409. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  410. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  411. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  412. package/src/plugins/impl/panel/utils.ts +17 -0
  413. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +2 -5
  414. package/src/plugins/impl/plotly/selection.ts +25 -19
  415. package/src/plugins/impl/vega/VegaPlugin.tsx +2 -3
  416. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  417. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  418. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  419. package/src/stories/button.stories.tsx +1 -0
  420. package/src/stories/editor.stories.tsx +24 -17
  421. package/src/stories/form-wrapper.stories.tsx +2 -2
  422. package/src/stories/select.stories.tsx +2 -2
  423. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  424. package/src/utils/__tests__/arrays.test.ts +35 -0
  425. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  426. package/src/utils/__tests__/numbers.test.ts +48 -1
  427. package/src/utils/__tests__/objects.test.ts +21 -0
  428. package/src/utils/arrays.ts +10 -0
  429. package/src/utils/blob.ts +1 -1
  430. package/src/utils/numbers.ts +28 -17
  431. package/src/utils/objects.ts +3 -7
  432. package/src/utils/once.ts +19 -19
  433. package/src/utils/tracer.ts +9 -5
  434. package/src/utils/zod-utils.ts +1 -3
  435. package/dist/assets/CellStatus-DVOHpdeJ.js +0 -1
  436. package/dist/assets/JsonOutput-Cx1iyibB.js +0 -11
  437. package/dist/assets/MarimoErrorOutput-s_5GOX8k.js +0 -10
  438. package/dist/assets/RunButton-BKoh0zYG.js +0 -1
  439. package/dist/assets/__vite-browser-external-CYO_P3S8.js +0 -1
  440. package/dist/assets/__vite-browser-external-FeDVGZU8.js +0 -1
  441. package/dist/assets/ai-model-dropdown-i1vfGeVi.js +0 -13
  442. package/dist/assets/any-language-editor-BdTWdMCI.js +0 -1
  443. package/dist/assets/arrays-BqUx5USx.js +0 -1
  444. package/dist/assets/cell-editor-BKJ06H5F.js +0 -21
  445. package/dist/assets/chat-panel-Cau71Ygr.js +0 -4
  446. package/dist/assets/chat-ui-C5ql1Up-.js +0 -4
  447. package/dist/assets/circle-play-CYIb0qT6.js +0 -1
  448. package/dist/assets/column-preview-BZ_iNhtJ.js +0 -1
  449. package/dist/assets/command-palette-DDC3kBet.js +0 -1
  450. package/dist/assets/components-YBmtV0p6.js +0 -1
  451. package/dist/assets/config-Dy28fDwn.js +0 -1
  452. package/dist/assets/edit-page-D_Rg0gQO.js +0 -9
  453. package/dist/assets/feedback-button-Kla7i5RT.js +0 -1
  454. package/dist/assets/file-explorer-panel-Uk4_QIJQ.js +0 -1
  455. package/dist/assets/home-page-hmvKlc1w.js +0 -4
  456. package/dist/assets/index-6b4mf8Ai.css +0 -2
  457. package/dist/assets/index-ptK1gxmo.js +0 -30
  458. package/dist/assets/kiosk-mode-BZECfg9k.js +0 -1
  459. package/dist/assets/linear-0eij_aAG.js +0 -1
  460. package/dist/assets/link-NuFVR7G9.js +0 -1
  461. package/dist/assets/links-DkkL9DVa.js +0 -1
  462. package/dist/assets/numbers-Mk3-CcoU.js +0 -1
  463. package/dist/assets/packages-panel-CI5ff9b-.js +0 -1
  464. package/dist/assets/panels-BCcZ_yLK.js +0 -107
  465. package/dist/assets/precisionRound-Bq_nmf5u.js +0 -1
  466. package/dist/assets/process-output-f3C3PD0Z.js +0 -1
  467. package/dist/assets/react-vega-C3rYGtP6.js +0 -189
  468. package/dist/assets/requests-CgSY1X4a.js +0 -1
  469. package/dist/assets/run-page-AfZhtEh1.js +0 -1
  470. package/dist/assets/scratchpad-panel-D2rWg4k8.js +0 -1
  471. package/dist/assets/session-panel-CnANhcTo.js +0 -1
  472. package/dist/assets/state-BAJYRhmO.js +0 -1
  473. package/dist/assets/state-CbjBtkdd.js +0 -3
  474. package/dist/assets/tooltip-mT3CGest.js +0 -61
  475. package/dist/assets/tracing-Dz2rYOFL.js +0 -1
  476. package/dist/assets/tracing-panel-DqQY9V91.js +0 -2
  477. package/dist/assets/useAsyncData-C7aJJz0Y.js +0 -1
  478. package/dist/assets/useBoolean-d0Tty94N.js +0 -1
  479. package/dist/assets/useCellActionButton-DmaFZ4oF.js +0 -1
  480. package/dist/assets/useDebounce-B_ccBZTp.js +0 -1
  481. package/dist/assets/useNotebookActions-DHt3s-ia.js +0 -1
  482. package/dist/assets/vega-component-IouMe13t.js +0 -1
  483. package/dist/assets/vega-loader.browser-CLRkNgm7.js +0 -6
  484. package/dist/assets/worker-DTQ7oH2A.js +0 -158
  485. package/dist/assets/ws-BXWY541V.js +0 -22
  486. package/src/core/host/__tests__/events.test.ts +0 -46
  487. package/src/core/host/__tests__/protocol.test.ts +0 -214
  488. package/src/core/host/events.ts +0 -60
  489. package/src/core/host/protocol.ts +0 -1943
  490. package/src/core/state/__tests__/jotai.test.ts +0 -25
  491. package/src/core/websocket/__tests__/createConnectionTransport.test.ts +0 -49
  492. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  493. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -335
  494. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  495. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  496. package/src/core/websocket/transports/basic.ts +0 -102
  497. package/src/core/websocket/transports/create.ts +0 -31
  498. package/src/core/websocket/transports/sse.ts +0 -308
  499. package/src/core/websocket/transports/transport.ts +0 -50
  500. package/src/core/websocket/transports/ws.ts +0 -98
@@ -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("../transports/notebook", () => ({
25
+ createNotebookTransport: 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,72 @@ 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 { createNotebookTransport } from "../transports/notebook";
62
55
  import { useNotebookSession } from "../useNotebookSession";
63
- import { createConnectionTransport } from "../transports/create";
56
+ import { type NotebookTransport, StreamStopped } from "@marimo-team/client";
64
57
 
65
58
  const SESSION_ID = generateSessionId();
66
59
 
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"]>;
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;
74
66
  }
75
67
 
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
- };
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 };
87
82
  }
88
83
 
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
- }
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
+ };
104
104
  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),
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
+ ),
109
119
  };
110
120
  }
111
121
 
@@ -147,12 +157,12 @@ function renderConnectionHook(store: ReturnType<typeof createStore>) {
147
157
  }
148
158
 
149
159
  describe("useNotebookSession.reconnect()", () => {
150
- let transport: MockTransport;
160
+ let fake: FakeTransport;
151
161
  let isHealthy: Mock<() => Promise<boolean>>;
152
162
  let store: ReturnType<typeof createStore>;
153
163
 
154
164
  beforeEach(() => {
155
- transport = makeTransport(WebSocket.CLOSED);
165
+ fake = withTransport();
156
166
  isHealthy = vi.fn<() => Promise<boolean>>().mockResolvedValue(true);
157
167
  store = createStore();
158
168
  store.set(connectionAtom, {
@@ -160,55 +170,53 @@ describe("useNotebookSession.reconnect()", () => {
160
170
  code: WebSocketClosedReason.KERNEL_DISCONNECTED,
161
171
  reason: "kernel not found",
162
172
  });
163
- vi.mocked(createConnectionTransport).mockReturnValue(transport);
164
173
  vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager(isHealthy));
165
174
  });
166
175
 
167
- function renderUseHook() {
168
- 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;
169
181
  }
170
182
 
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
- });
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
+ );
190
198
 
191
199
  it("probes /health and reconnects when the runtime is healthy", async () => {
192
200
  isHealthy.mockResolvedValue(true);
193
- const { result } = renderUseHook();
201
+ const { result } = await renderStopped();
194
202
  await act(async () => {
195
203
  await result.current.reconnect();
196
204
  });
197
205
  expect(isHealthy).toHaveBeenCalledOnce();
198
- expect(transport.reconnect).toHaveBeenCalledOnce();
206
+ expect(fake.attempts).toHaveLength(2);
199
207
  expect(store.get(connectionAtom)).toEqual({
200
208
  state: ConnectionState.CONNECTING,
201
209
  });
202
210
  });
203
211
 
204
- 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 () => {
205
213
  isHealthy.mockResolvedValue(false);
206
- const { result } = renderUseHook();
214
+ const { result } = await renderStopped();
207
215
  await act(async () => {
208
216
  await result.current.reconnect();
209
217
  });
210
218
  expect(isHealthy).toHaveBeenCalledOnce();
211
- expect(transport.reconnect).not.toHaveBeenCalled();
219
+ expect(fake.attempts).toHaveLength(1);
212
220
  expect(store.get(connectionAtom)).toEqual({
213
221
  state: ConnectionState.CLOSED,
214
222
  code: WebSocketClosedReason.KERNEL_DISCONNECTED,
@@ -219,144 +227,30 @@ describe("useNotebookSession.reconnect()", () => {
219
227
 
220
228
  describe("useNotebookSession messages", () => {
221
229
  let store: ReturnType<typeof createStore>;
230
+ let fake: FakeTransport;
222
231
 
223
- beforeEach(() => {
232
+ beforeEach(async () => {
224
233
  store = createStore();
225
- vi.mocked(createConnectionTransport).mockClear();
226
- vi.mocked(createConnectionTransport).mockReturnValue(
227
- makeTransport(WebSocket.OPEN),
228
- );
234
+ fake = withTransport();
229
235
  vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
230
236
  renderConnectionHook(store);
237
+ await started();
231
238
  });
232
239
 
233
240
  function receive(payload: unknown) {
234
- const options = getTransportHandlers();
235
- act(() => {
236
- options.onMessage(
237
- new MessageEvent("message", { data: JSON.stringify(payload) }),
238
- );
239
- });
241
+ act(() => connection(fake).receive(payload));
240
242
  }
241
243
 
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
- }
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"];
251
+ expect(store.get(initialRunCompletedAtom)).toBe(false);
252
+ receive({ id: "42", event: "marimo", data: { marimo: notification } });
253
+ expect(store.get(initialRunCompletedAtom)).toBe(true);
360
254
  });
361
255
  });
362
256
 
@@ -365,190 +259,35 @@ it.each(["kernel-ready", "reconnected"])(
365
259
  async (op) => {
366
260
  const store = createStore();
367
261
  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);
262
+ const fake = withTransport();
427
263
  vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
428
264
  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
- });
265
+ await started();
266
+ const conn = connection(fake);
465
267
 
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([]);
268
+ await act(async () => conn.open());
470
269
  expect(store.get(connectionAtom).state).toBe(ConnectionState.CONNECTING);
471
- expect(transport.reconnect).toHaveBeenCalledOnce();
472
270
 
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
271
  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
- ),
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
+ }),
550
289
  );
551
- expect(store.get(attachmentsAtom)).toEqual(resumed ? attachments : []);
290
+ expect(store.get(connectionAtom).state).toBe(ConnectionState.OPEN);
552
291
  },
553
292
  );
554
293
 
@@ -556,11 +295,10 @@ describe("connection notice", () => {
556
295
  beforeEach(() => vi.useFakeTimers());
557
296
  afterEach(() => vi.useRealTimers());
558
297
 
559
- function renderNotice() {
298
+ async function renderNotice() {
560
299
  const store = createStore();
561
300
  store.set(connectionAtom, { state: ConnectionState.CONNECTING });
562
- const transport = makeTransport(WebSocket.OPEN);
563
- vi.mocked(createConnectionTransport).mockReturnValue(transport);
301
+ const fake = withTransport();
564
302
  vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
565
303
  const Connection = () => {
566
304
  const { reconnect } = useNotebookSession({
@@ -582,20 +320,17 @@ describe("connection notice", () => {
582
320
  </ErrorBoundary>
583
321
  </JotaiProvider>,
584
322
  );
585
- const options = getTransportHandlers();
323
+ await started();
324
+ const conn = connection(fake);
325
+ // Messages arrive on an open connection.
326
+ act(() => conn.open());
586
327
  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 };
328
+ act(() => conn.send(data));
329
+ return { store, fake, conn, send };
595
330
  }
596
331
 
597
- it("keeps elapsed time across startup phases and leaves reconnection to the footer once cells are available", () => {
598
- 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();
599
334
  send({
600
335
  op: "startup-progress",
601
336
  phase: "preparing-environment",
@@ -635,7 +370,7 @@ describe("connection notice", () => {
635
370
  expect(
636
371
  screen.queryByRole("region", { name: "Notebook startup" }),
637
372
  ).not.toBeInTheDocument();
638
- act(() => options.onClose(new CloseEvent("close")));
373
+ await act(async () => conn.drop());
639
374
  act(() => vi.advanceTimersByTime(500));
640
375
  expect(store.get(connectionAtom)).toEqual({
641
376
  state: ConnectionState.CONNECTING,
@@ -646,8 +381,8 @@ describe("connection notice", () => {
646
381
  ).not.toBeInTheDocument();
647
382
  });
648
383
 
649
- it("restores startup output before appending live chunks and resets it for a new attempt", () => {
650
- 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();
651
386
  const snapshot = {
652
387
  op: "startup-progress",
653
388
  phase: "starting-kernel",
@@ -673,7 +408,7 @@ describe("connection notice", () => {
673
408
  });
674
409
 
675
410
  it("restores completed startup output without putting a connected kernel back into startup", async () => {
676
- const { store, options, send } = renderNotice();
411
+ const { store, conn, send } = await renderNotice();
677
412
  send({ op: "reconnected" });
678
413
  send({
679
414
  op: "startup-progress",
@@ -691,13 +426,15 @@ describe("connection notice", () => {
691
426
  screen.queryByRole("region", { name: "Notebook startup" }),
692
427
  ).not.toBeInTheDocument();
693
428
 
694
- // A new transport starts from the next session's authoritative snapshot.
695
- 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());
696
433
  expect(store.get(startupProgressAtom)).toBeNull();
697
434
  });
698
435
 
699
- it("does not flash a notice when startup finishes within the delay", () => {
700
- const { send } = renderNotice();
436
+ it("does not flash a notice when startup finishes within the delay", async () => {
437
+ const { send } = await renderNotice();
701
438
  send({
702
439
  op: "startup-progress",
703
440
  phase: "preparing-environment",
@@ -725,16 +462,11 @@ describe("connection notice", () => {
725
462
  it.each(["preparing-environment", "starting-kernel"] as const)(
726
463
  "keeps a %s failure available until the user retries",
727
464
  async (phase) => {
728
- const { store, transport, options, send } = renderNotice();
465
+ const { store, fake, conn, send } = await renderNotice();
729
466
  send({ op: "startup-progress", phase, logs: "", log_mode: "replace" });
730
467
  const error = "A full diagnostic\nwith <stderr> details";
731
468
  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
- );
469
+ await act(async () => conn.close("MARIMO_KERNEL_STARTUP_ERROR"));
738
470
  expect(screen.getByRole("status")).toHaveTextContent(
739
471
  "Kernel failed to start",
740
472
  );
@@ -745,11 +477,11 @@ describe("connection notice", () => {
745
477
  expect(screen.getByLabelText("Error details")).toHaveTextContent(error, {
746
478
  normalizeWhitespace: false,
747
479
  });
748
- expect(transport.reconnect).not.toHaveBeenCalled();
480
+ expect(fake.attempts).toHaveLength(1);
749
481
  await act(async () =>
750
482
  fireEvent.click(screen.getByRole("button", { name: "Try again" })),
751
483
  );
752
- expect(transport.reconnect).toHaveBeenCalledOnce();
484
+ expect(fake.attempts).toHaveLength(2);
753
485
  expect(store.get(kernelStartupErrorAtom)).toBeNull();
754
486
  expect(
755
487
  screen.queryByRole("button", { name: "Try again" }),
@@ -769,23 +501,14 @@ describe("connection notice", () => {
769
501
  );
770
502
  });
771
503
 
772
- it("replaces environment state from snapshots, then continues live progress", () => {
504
+ it("replaces environment state from snapshots, then continues live progress", async () => {
773
505
  const store = createStore();
774
- vi.mocked(createConnectionTransport).mockClear();
775
- vi.mocked(createConnectionTransport).mockReturnValue(
776
- makeTransport(WebSocket.OPEN),
777
- );
506
+ const fake = withTransport();
778
507
  vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
779
508
  renderConnectionHook(store);
780
- const options = getTransportHandlers();
509
+ await started();
781
510
  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
- );
511
+ act(() => connection(fake).send(data));
789
512
  }
790
513
  receive({
791
514
  op: "environment-operation",