@marimo-team/frontend 0.25.3-dev10 → 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 (452) 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-B4n9EnHB.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-9bMgbYmo.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-f_MnLCwI.js → add-cell-with-ai-Clg5FELx.js} +7 -7
  12. package/dist/assets/{add-connection-dialog-CoE_PhuD.js → add-connection-dialog-BqdWmD-7.js} +2 -2
  13. package/dist/assets/{agent-panel-BWZz24jK.js → agent-panel-5RxG5Y82.js} +3 -3
  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-BhU0ulYs.js → app-config-button-0QTeQ2Mv.js} +1 -1
  18. package/dist/assets/arrays-0R2reF2y.js +1 -0
  19. package/dist/assets/{blob-iOBwGaP9.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-rA_jsZCj.js → cells-D_11MjVB.js} +60 -60
  26. package/dist/assets/{chat-display-BmEPmhDP.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-lisZ0l6a.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-Dvd1GnD6.js → components-G6frbw0c.js} +1 -1
  38. package/dist/assets/config-CD1IuZFJ.js +1 -0
  39. package/dist/assets/{config-DJLAC21v.js → config-DMz7oN0r.js} +1 -1
  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-Dr1xNKUn.js → datasource-deuXnlU-.js} +1 -1
  45. package/dist/assets/{dates-CfmdTL6B.js → dates-CQMwFko2.js} +1 -1
  46. package/dist/assets/{dependency-graph-panel-B0uP-_id.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-DVl_ebWy.js → documentation-panel-2PQ8kQLa.js} +1 -1
  49. package/dist/assets/{download-Bx55LMki.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-DOWpdGvI.js → error-entries-3GVk2KjS.js} +1 -1
  54. package/dist/assets/{error-panel-DpbNKSLs.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-B99ThNJW.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-ChnBsDE_.js → file-name-input-DqwMSgGU.js} +1 -1
  61. package/dist/assets/{floating-outline-MYqQW4Vp.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-DSL0J4md.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-B1MWlKeM.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-BIogTGCr.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-y0XnfulJ.js → logs-panel-B5eVAZtH.js} +1 -1
  80. package/dist/assets/{markdown-renderer-Q5AhbRU3.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-BNOnF6-0.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-Be7stWwv.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-GeMEdCPw.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-YULOXf4h.js → readonly-code-display-DYcZQ28T.js} +1 -1
  97. package/dist/assets/{readonly-python-code-CadgXgRu.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-BVAR7ayZ.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-DuChzsA6.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-CQ1dn3lA.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-Bfh_2SLl.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-DAd80MbP.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-DGstJK0S.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-DFBGlSQk.js → useDeleteCell-BC-MW7d1.js} +1 -1
  142. package/dist/assets/{useDependencyPanelTab-DydxsE5d.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-BFjFX3hz.js → useRunCells-ByaWxoIt.js} +1 -1
  150. package/dist/assets/{useSplitCell-Tqv5pPtL.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 -78
  161. package/package.json +7 -7
  162. package/src/__tests__/host-imports.test.ts +1 -0
  163. package/src/components/app-config/ai-config.tsx +7 -2
  164. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  165. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  166. package/src/components/chat/acp/blocks.tsx +2 -2
  167. package/src/components/chat/chat-components.tsx +2 -0
  168. package/src/components/chat/chat-panel.tsx +9 -8
  169. package/src/components/chat/chat-utils.ts +4 -8
  170. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  171. package/src/components/data-table/__tests__/columns.test.tsx +105 -0
  172. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  173. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  174. package/src/components/data-table/__tests__/export-actions.test.tsx +105 -0
  175. package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
  176. package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
  177. package/src/components/data-table/cell-selection/feature.ts +2 -2
  178. package/src/components/data-table/cell-selection/types.ts +2 -2
  179. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
  180. package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
  181. package/src/components/data-table/charts/charts.tsx +35 -25
  182. package/src/components/data-table/charts/storage.ts +16 -5
  183. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  184. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  185. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  186. package/src/components/data-table/columns.tsx +45 -46
  187. package/src/components/data-table/context-menu.tsx +36 -20
  188. package/src/components/data-table/data-table.tsx +7 -1
  189. package/src/components/data-table/date-popover.tsx +6 -4
  190. package/src/components/data-table/export-actions.tsx +76 -48
  191. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +2 -2
  192. package/src/components/data-table/search-highlight.tsx +79 -0
  193. package/src/components/data-table/types.ts +1 -0
  194. package/src/components/data-table/url-detector.tsx +49 -9
  195. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  196. package/src/components/datasources/__tests__/column-preview.test.tsx +63 -2
  197. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  198. package/src/components/datasources/column-preview.tsx +8 -2
  199. package/src/components/datasources/datasources.tsx +47 -35
  200. package/src/components/debugger/debugger-code.tsx +5 -0
  201. package/src/components/dependency-graph/panels.tsx +2 -1
  202. package/src/components/editor/SortableCell.tsx +1 -3
  203. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  204. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +24 -0
  205. package/src/components/editor/actions/pair-with-agent-modal.tsx +18 -15
  206. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  207. package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
  208. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  209. package/src/components/editor/cell/CreateCellButton.tsx +8 -10
  210. package/src/components/editor/cell/DeleteButton.tsx +1 -0
  211. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  212. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  213. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  214. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  215. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  216. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
  217. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  218. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  219. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +12 -18
  220. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +58 -1
  221. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  222. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
  223. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  224. package/src/components/editor/connections/components.tsx +1 -1
  225. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  226. package/src/components/editor/controls/command-palette.tsx +22 -10
  227. package/src/components/editor/dynamic-favicon.tsx +21 -40
  228. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +12 -23
  229. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  230. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  231. package/src/components/editor/file-tree/file-explorer.tsx +10 -5
  232. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  233. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  234. package/src/components/editor/notebook-banner.tsx +1 -0
  235. package/src/components/editor/output/MarimoTracebackOutput.tsx +2 -2
  236. package/src/components/editor/output/ansi-reduce.ts +6 -0
  237. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  238. package/src/components/editor/package-alert.tsx +6 -4
  239. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  240. package/src/components/home/components.tsx +17 -31
  241. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  242. package/src/components/icons/large-spinner.tsx +3 -14
  243. package/src/components/icons/spinner.tsx +1 -1
  244. package/src/components/mcp/mcp-status-indicator.tsx +1 -1
  245. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  246. package/src/components/pages/home-page.tsx +164 -91
  247. package/src/components/scratchpad/scratchpad.tsx +2 -2
  248. package/src/components/slides/reveal-component.tsx +4 -2
  249. package/src/components/slides/slide-cell-view.tsx +1 -1
  250. package/src/components/slides/slide-form.tsx +1 -0
  251. package/src/components/tracing/tracing.tsx +2 -2
  252. package/src/components/ui/alert-dialog.tsx +1 -1
  253. package/src/core/ai/context/providers/datasource.ts +6 -2
  254. package/src/core/attachments/state.ts +5 -8
  255. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -0
  256. package/src/core/cells/__tests__/cell.test.ts +39 -0
  257. package/src/core/cells/__tests__/cells.test.ts +3 -1
  258. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
  259. package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
  260. package/src/core/cells/cell.ts +14 -3
  261. package/src/core/cells/cells.ts +3 -1
  262. package/src/core/cells/pending-delete-service.ts +2 -6
  263. package/src/core/cells/session.ts +1 -1
  264. package/src/core/cells/types.ts +10 -1
  265. package/src/core/codemirror/__tests__/setup.test.ts +1 -1
  266. package/src/core/codemirror/keymaps/vim.ts +2 -6
  267. package/src/core/codemirror/language/languages/markdown.ts +1 -1
  268. package/src/core/codemirror/language/languages/python.ts +3 -3
  269. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  270. package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
  271. package/src/core/datasets/data-source-discovery.ts +4 -6
  272. package/src/core/document/__tests__/draft.test.ts +322 -0
  273. package/src/core/document/__tests__/fixtures.ts +92 -0
  274. package/src/core/document/__tests__/model.test.ts +483 -0
  275. package/src/core/document/__tests__/state.test.ts +446 -0
  276. package/src/core/document/__tests__/submission.test.ts +346 -0
  277. package/src/core/document/draft.ts +472 -0
  278. package/src/core/document/index.ts +10 -0
  279. package/src/core/document/model.ts +53 -0
  280. package/src/core/document/state.ts +401 -0
  281. package/src/core/document/sync.ts +359 -0
  282. package/src/core/edit-app.tsx +2 -2
  283. package/src/core/host/AGENTS.md +12 -2
  284. package/src/core/host/__tests__/attachments.test.ts +56 -0
  285. package/src/core/host/__tests__/document.test.ts +126 -0
  286. package/src/core/host/__tests__/fixtures.ts +41 -0
  287. package/src/core/host/__tests__/notebook-client.test.ts +570 -0
  288. package/src/core/host/connection.ts +42 -0
  289. package/src/core/host/notebook-client.ts +297 -0
  290. package/src/core/host/resources/attachments.ts +22 -0
  291. package/src/core/host/resources/document.ts +101 -0
  292. package/src/core/hotkeys/actions.ts +2 -4
  293. package/src/core/islands/__tests__/bootstrap.test.ts +0 -26
  294. package/src/core/islands/bootstrap.ts +0 -1
  295. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  296. package/src/core/network/connection.ts +4 -2
  297. package/src/core/network/useConnectionNotice.ts +8 -2
  298. package/src/core/run-app.tsx +2 -2
  299. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  300. package/src/core/runtime/__tests__/runtime.test.ts +18 -17
  301. package/src/core/runtime/runtime.ts +26 -30
  302. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  303. package/src/core/state/jotai.ts +1 -1
  304. package/src/core/static/files.ts +2 -0
  305. package/src/core/wasm/bridge.ts +2 -10
  306. package/src/core/wasm/store.ts +3 -3
  307. package/src/core/wasm/worker/bootstrap.ts +1 -1
  308. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  309. package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -29
  310. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +223 -0
  311. package/src/core/websocket/__tests__/useNotebookSession.hook.test.tsx +606 -0
  312. package/src/core/websocket/connection-state.ts +60 -0
  313. package/src/core/websocket/state.ts +7 -0
  314. package/src/core/websocket/transports/__tests__/notebook.test.ts +502 -0
  315. package/src/core/websocket/transports/notebook.ts +213 -0
  316. package/src/core/websocket/types.ts +9 -39
  317. package/src/core/websocket/useNotebookClient.ts +89 -0
  318. package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +53 -233
  319. package/src/css/globals.css +5 -0
  320. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  321. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  322. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  323. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  324. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  325. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  326. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  327. package/src/hooks/useAsyncData.ts +3 -3
  328. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  329. package/src/hooks/useDebounce.ts +5 -8
  330. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  331. package/src/hooks/useDelayElapsed.ts +14 -10
  332. package/src/hooks/useElapsedTime.ts +3 -3
  333. package/src/hooks/useInputHistory.ts +4 -7
  334. package/src/hooks/useInternalStateWithSync.ts +6 -6
  335. package/src/hooks/useResizeObserver.ts +4 -7
  336. package/src/hooks/useScript.ts +67 -70
  337. package/src/hooks/useStateForKey.ts +93 -0
  338. package/src/plugins/core/registerReactComponent.tsx +1 -1
  339. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  340. package/src/plugins/impl/DataTablePlugin.tsx +14 -15
  341. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  342. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  343. package/src/plugins/impl/FormPlugin.tsx +1 -1
  344. package/src/plugins/impl/RangeSliderPlugin.tsx +0 -1
  345. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  346. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  347. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +117 -1
  348. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  349. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  350. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
  351. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  352. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  353. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  354. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  355. package/src/plugins/impl/chat/chat-ui.tsx +12 -10
  356. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  357. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  358. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  359. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  360. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  361. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +0 -1
  362. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  363. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  364. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  365. package/src/plugins/impl/panel/utils.ts +17 -0
  366. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  367. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  368. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  369. package/src/stories/button.stories.tsx +1 -0
  370. package/src/stories/editor.stories.tsx +24 -17
  371. package/src/stories/form-wrapper.stories.tsx +2 -2
  372. package/src/stories/select.stories.tsx +2 -2
  373. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  374. package/src/utils/__tests__/arrays.test.ts +35 -0
  375. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  376. package/src/utils/__tests__/numbers.test.ts +48 -1
  377. package/src/utils/__tests__/objects.test.ts +21 -0
  378. package/src/utils/arrays.ts +10 -0
  379. package/src/utils/blob.ts +1 -1
  380. package/src/utils/numbers.ts +28 -17
  381. package/src/utils/objects.ts +3 -7
  382. package/src/utils/once.ts +19 -19
  383. package/src/utils/zod-utils.ts +1 -3
  384. package/dist/assets/CellStatus-nZpuDkm7.js +0 -1
  385. package/dist/assets/JsonOutput-BxB0NL8w.js +0 -11
  386. package/dist/assets/MarimoErrorOutput-BTy4MOzX.js +0 -10
  387. package/dist/assets/RunButton-CFQUNN5c.js +0 -1
  388. package/dist/assets/__vite-browser-external-CYO_P3S8.js +0 -1
  389. package/dist/assets/__vite-browser-external-FeDVGZU8.js +0 -1
  390. package/dist/assets/ai-model-dropdown-zm-2fXzf.js +0 -13
  391. package/dist/assets/any-language-editor-BLCAD3yx.js +0 -1
  392. package/dist/assets/arrays-BqUx5USx.js +0 -1
  393. package/dist/assets/cell-editor-CAj1UKxx.js +0 -21
  394. package/dist/assets/cell-link-1CynTVGQ.js +0 -1
  395. package/dist/assets/chat-panel-Ce2Xh7dM.js +0 -4
  396. package/dist/assets/chat-ui-DTy98rP3.js +0 -4
  397. package/dist/assets/circle-play-CYIb0qT6.js +0 -1
  398. package/dist/assets/code-visibility-B-_GvwR5.js +0 -1
  399. package/dist/assets/coerce-CpE6kUdX.js +0 -1
  400. package/dist/assets/column-preview-DU6dT80y.js +0 -1
  401. package/dist/assets/command-palette-BMqFKXVr.js +0 -1
  402. package/dist/assets/components-CXfO-F47.js +0 -1
  403. package/dist/assets/config-DX8Y2WbJ.js +0 -1
  404. package/dist/assets/config-VPSq73n8.js +0 -1
  405. package/dist/assets/edit-page-DlfCLUaq.js +0 -9
  406. package/dist/assets/feedback-button-DlqZkJwz.js +0 -1
  407. package/dist/assets/file-explorer-panel-Bo9eN3rW.js +0 -1
  408. package/dist/assets/home-page-KrhQ55Vg.js +0 -4
  409. package/dist/assets/index-6b4mf8Ai.css +0 -2
  410. package/dist/assets/index-B3fcrmuf.js +0 -30
  411. package/dist/assets/kiosk-mode-BZECfg9k.js +0 -1
  412. package/dist/assets/layout-DOpdUwym.js +0 -9
  413. package/dist/assets/linear-0eij_aAG.js +0 -1
  414. package/dist/assets/link-NuFVR7G9.js +0 -1
  415. package/dist/assets/links-DkkL9DVa.js +0 -1
  416. package/dist/assets/numbers-Mk3-CcoU.js +0 -1
  417. package/dist/assets/packages-panel-ClxOAEGy.js +0 -1
  418. package/dist/assets/panels-DfCmzLtT.js +0 -4
  419. package/dist/assets/precisionRound-Bq_nmf5u.js +0 -1
  420. package/dist/assets/process-output-DNFKMODr.js +0 -1
  421. package/dist/assets/react-vega-C3rYGtP6.js +0 -189
  422. package/dist/assets/requests-CgSY1X4a.js +0 -1
  423. package/dist/assets/run-page-Cxu1yV8Q.js +0 -1
  424. package/dist/assets/scratchpad-panel-By-ybhjg.js +0 -1
  425. package/dist/assets/session-panel-BmW6PH4z.js +0 -1
  426. package/dist/assets/state-BAJYRhmO.js +0 -1
  427. package/dist/assets/state-z3dlofZB.js +0 -3
  428. package/dist/assets/tooltip-mT3CGest.js +0 -61
  429. package/dist/assets/tracing-BARlJ_Dk.js +0 -1
  430. package/dist/assets/tracing-panel-dgnr2fIN.js +0 -2
  431. package/dist/assets/useAsyncData-C7aJJz0Y.js +0 -1
  432. package/dist/assets/useBoolean-BDD6_xQQ.js +0 -1
  433. package/dist/assets/useCellActionButton-DY9RlJTb.js +0 -1
  434. package/dist/assets/useDebounce-B_ccBZTp.js +0 -1
  435. package/dist/assets/useNotebookActions-DfvB7RD_.js +0 -1
  436. package/dist/assets/vega-component-CnadTgoE.js +0 -1
  437. package/dist/assets/vega-loader.browser-CLRkNgm7.js +0 -6
  438. package/dist/assets/ws-BXWY541V.js +0 -22
  439. package/src/core/host/__tests__/protocol.test.ts +0 -214
  440. package/src/core/host/protocol.ts +0 -1943
  441. package/src/core/state/__tests__/jotai.test.ts +0 -25
  442. package/src/core/websocket/__tests__/useMarimoKernelConnection.hook.test.tsx +0 -789
  443. package/src/core/websocket/__tests__/useWebSocket.test.ts +0 -49
  444. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  445. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -338
  446. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  447. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  448. package/src/core/websocket/transports/basic.ts +0 -102
  449. package/src/core/websocket/transports/sse.ts +0 -308
  450. package/src/core/websocket/transports/transport.ts +0 -61
  451. package/src/core/websocket/transports/ws.ts +0 -98
  452. package/src/core/websocket/useWebSocket.tsx +0 -88
@@ -1,11 +1,10 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import { invalidatePackageData } from "@/core/packages/package-data";
4
- import { useAtom, useAtomValue, useSetAtom } from "jotai";
5
- import { useRef } from "react";
4
+ import { useAtom, useAtomValue, useSetAtom, useStore } from "jotai";
5
+ import { useLayoutEffect } from "react";
6
6
  import { useErrorBoundary } from "react-error-boundary";
7
7
  import { toast } from "@/components/ui/use-toast";
8
- import { attachmentsAtom } from "@/core/attachments/state";
9
8
  import { getNotebook, useCellActions } from "@/core/cells/cells";
10
9
  import { applyTransactionChanges } from "@/core/cells/document-changes";
11
10
  import { AUTOCOMPLETER } from "@/core/codemirror/completion/Autocompleter";
@@ -13,8 +12,7 @@ import type {
13
12
  NotificationMessageData,
14
13
  NotificationPayload,
15
14
  } from "@/core/kernel/messages";
16
- import { TRANSPORT_EXHAUSTED_REASON } from "@/core/websocket/transports/ws";
17
- import { useConnectionTransport } from "@/core/websocket/useWebSocket";
15
+ import { useNotebookClient } from "@/core/websocket/useNotebookClient";
18
16
  import { renderHTML } from "@/plugins/core/RenderHTML";
19
17
  import {
20
18
  handleWidgetMessage,
@@ -22,11 +20,7 @@ import {
22
20
  } from "@/plugins/impl/anywidget/registry";
23
21
  import { logNever } from "@/utils/assertNever";
24
22
  import { prettyError } from "@/utils/errors";
25
- import {
26
- type JsonString,
27
- safeExtractSetUIElementMessageBuffers,
28
- } from "@/utils/json/base64";
29
- import { jsonParseWithSpecialChar } from "@/utils/json/json-parser";
23
+ import { safeExtractSetUIElementMessageBuffers } from "@/utils/json/base64";
30
24
  import { Logger } from "@/utils/Logger";
31
25
  import { reloadSafe } from "@/utils/reload-safe";
32
26
  import { useAlertActions } from "../alerts/state";
@@ -82,104 +76,10 @@ import { useStorageActions } from "../storage/state";
82
76
  import { useVariablesActions } from "../variables/state";
83
77
  import type { VariableName } from "../variables/types";
84
78
  import { isWasm } from "../wasm/utils";
85
- import {
86
- type ConnectionStatus,
87
- WebSocketClosedReason,
88
- ConnectionState,
89
- } from "./types";
79
+ import { WebSocketClosedReason, ConnectionState } from "./types";
90
80
 
91
81
  const SUPPORTS_LAZY_KERNELS = true;
92
82
 
93
- // All MARIMO_* reasons except TRANSPORT_EXHAUSTED are emitted by the backend
94
- // (marimo/_server/api/endpoints/ws_endpoint.py and ws/*.py). Keep in sync with
95
- // the backend literals. Over the SSE transport these arrive as in-band
96
- // `close` events rather than WebSocket close frames, so every terminal
97
- // reason must be classified here — an unclassified reason falls into the
98
- // retry path.
99
- export type CloseReason =
100
- | "MARIMO_NO_FILE_KEY"
101
- | "MARIMO_NO_SESSION_ID"
102
- | "MARIMO_NO_SESSION"
103
- | "MARIMO_SHUTDOWN"
104
- | "MARIMO_KERNEL_STARTUP_ERROR"
105
- | "MARIMO_UNAUTHORIZED"
106
- | "MARIMO_KIOSK_NOT_ALLOWED"
107
- | typeof TRANSPORT_EXHAUSTED_REASON;
108
-
109
- export type CloseDecision =
110
- | { kind: "terminal"; status: ConnectionStatus; closeTransport: boolean }
111
- | { kind: "gave-up"; status: ConnectionStatus }
112
- | { kind: "retry"; status: ConnectionStatus };
113
-
114
- export function classifyCloseEvent(event: { reason?: string }): CloseDecision {
115
- switch (event.reason as CloseReason | undefined) {
116
- case TRANSPORT_EXHAUSTED_REASON:
117
- return {
118
- kind: "gave-up",
119
- status: {
120
- state: ConnectionState.CLOSED,
121
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
122
- reason: "kernel not found",
123
- },
124
- };
125
- case "MARIMO_NO_FILE_KEY":
126
- case "MARIMO_NO_SESSION_ID":
127
- case "MARIMO_NO_SESSION":
128
- case "MARIMO_SHUTDOWN":
129
- return {
130
- kind: "terminal",
131
- status: {
132
- state: ConnectionState.CLOSED,
133
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
134
- reason: "kernel not found",
135
- },
136
- closeTransport: true,
137
- };
138
- case "MARIMO_UNAUTHORIZED":
139
- return {
140
- kind: "terminal",
141
- status: {
142
- state: ConnectionState.CLOSED,
143
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
144
- reason: "not authorized",
145
- },
146
- closeTransport: true,
147
- };
148
- case "MARIMO_KIOSK_NOT_ALLOWED":
149
- return {
150
- kind: "terminal",
151
- status: {
152
- state: ConnectionState.CLOSED,
153
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
154
- reason: "kiosk mode is not available for this session",
155
- },
156
- closeTransport: true,
157
- };
158
- case "MARIMO_KERNEL_STARTUP_ERROR":
159
- return {
160
- kind: "terminal",
161
- status: {
162
- state: ConnectionState.CLOSED,
163
- code: WebSocketClosedReason.KERNEL_STARTUP_ERROR,
164
- reason: "Failed to start kernel sandbox",
165
- },
166
- closeTransport: true,
167
- };
168
- default:
169
- // Empty/undefined reasons are normal transient closes. Anything else is
170
- // an unknown server reason; warn so a new MARIMO_* reason doesn't fall
171
- // silently into the retry path.
172
- if (event.reason) {
173
- logNever(event.reason as never);
174
- }
175
- }
176
-
177
- return {
178
- kind: "retry",
179
- status: { state: ConnectionState.CONNECTING },
180
- };
181
- }
182
-
183
83
  function getExistingCells(): CellData[] | undefined {
184
84
  if (!SUPPORTS_LAZY_KERNELS) {
185
85
  return undefined;
@@ -191,16 +91,12 @@ function getExistingCells(): CellData[] | undefined {
191
91
  );
192
92
  }
193
93
 
194
- /**
195
- * Creates a connection to the Marimo kernel and handles incoming messages.
196
- */
197
- export function useMarimoKernelConnection(opts: {
94
+ /** Coordinate session startup and apply notifications to the editor. */
95
+ export function useNotebookSession(opts: {
198
96
  sessionId: SessionId;
199
97
  autoInstantiate: boolean;
200
98
  setCells: (cells: CellData[], layout: LayoutState) => void;
201
99
  }) {
202
- // Track whether we want to try reconnecting.
203
- const shouldTryReconnecting = useRef<boolean>(true);
204
100
  const { autoInstantiate, sessionId, setCells } = opts;
205
101
  const { showBoundary } = useErrorBoundary();
206
102
 
@@ -237,7 +133,6 @@ export function useMarimoKernelConnection(opts: {
237
133
  } = useAlertActions();
238
134
  const setKioskMode = useSetAtom(kioskModeAtom);
239
135
  const setStableSessionId = useSetAtom(stableSessionIdAtom);
240
- const setAttachments = useSetAtom(attachmentsAtom);
241
136
  const setCapabilities = useSetAtom(capabilitiesAtom);
242
137
  const runtimeManager = useRuntimeManager();
243
138
  const transportType = useAtomValue(connectionTransportTypeAtom);
@@ -248,15 +143,11 @@ export function useMarimoKernelConnection(opts: {
248
143
  filterFromVariables: filterStorageFromVariables,
249
144
  } = useStorageActions();
250
145
 
251
- const handleMessage = (e: MessageEvent<JsonString<NotificationPayload>>) => {
252
- const msg = jsonParseWithSpecialChar(e.data);
146
+ const handleNotification = (msg: NotificationPayload) => {
253
147
  switch (msg.data.op) {
254
148
  case "reload":
255
149
  reloadSafe();
256
150
  return;
257
- case "attachments":
258
- setAttachments(msg.data.attachments);
259
- return;
260
151
  case "startup-progress": {
261
152
  const { phase } = msg.data;
262
153
  updateStartupProgress(msg.data);
@@ -288,7 +179,6 @@ export function useMarimoKernelConnection(opts: {
288
179
  existingCells,
289
180
  });
290
181
  if (!msg.data.resumed) {
291
- setAttachments([]);
292
182
  // A freshly started kernel may expose a different environment
293
183
  // (e.g. new env vars); re-run discovery instead of reusing stale
294
184
  // suggestions from the previous kernel instance.
@@ -511,109 +401,54 @@ export function useMarimoKernelConnection(opts: {
511
401
  }
512
402
  };
513
403
 
514
- const tryReconnecting = (code?: number, reason?: string) => {
515
- // If not properly gated, we could try reconnecting forever if the
516
- // issue is not transient. So we want to try reconnecting only once after an
517
- // open connection is closed.
518
- if (shouldTryReconnecting.current) {
519
- shouldTryReconnecting.current = false;
520
- ws.reconnect(code, reason);
521
- }
404
+ const store = useStore();
405
+ const onMessageError = (error: unknown) => {
406
+ toast({
407
+ title: "Failed to handle message",
408
+ description: prettyError(error),
409
+ variant: "danger",
410
+ });
522
411
  };
523
-
524
- // Manual reconnect. Probes /health first to fail fast when the runtime
525
- // is unreachable, instead of waiting on partysocket's retry budget.
526
- const reconnect = async () => {
527
- if (
528
- ws.readyState === WebSocket.OPEN ||
529
- ws.readyState === WebSocket.CONNECTING
530
- ) {
531
- return;
532
- }
533
- shouldTryReconnecting.current = true;
534
- setKernelStartupError(null);
535
- setConnection({ state: ConnectionState.CONNECTING });
536
- const healthy = await runtimeManager.reconcileFromHealth();
537
- if (!healthy) {
538
- shouldTryReconnecting.current = false;
539
- setConnection({
540
- state: ConnectionState.CLOSED,
541
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
542
- reason: "kernel not found",
543
- });
544
- return;
545
- }
546
- ws.reconnect();
547
- };
548
-
549
- const ws = useConnectionTransport({
412
+ const notebook = useNotebookClient({
550
413
  static: isStaticNotebook(),
551
414
  transportType,
552
- /**
553
- * Unique URL for this session.
554
- */
555
415
  url: () =>
556
416
  transportType === "sse"
557
417
  ? runtimeManager.getSseURL(sessionId).toString()
558
418
  : runtimeManager.getWsURL(sessionId).toString(),
559
- /**
560
- * Auth headers for the SSE transport. WebSockets cannot send headers
561
- * and instead put the access token in the URL.
562
- */
419
+ // SSE uses headers for auth. WebSockets carry the token in the URL.
563
420
  headers: () => runtimeManager.headers(),
564
-
565
- /**
566
- * The transport is open; kernel-ready establishes session readiness.
567
- */
568
- onOpen: async () => {
569
- updateStartupProgress(null);
570
- setAttachments([]);
571
- // If we are open, we can reset our reconnecting flag.
572
- shouldTryReconnecting.current = true;
573
- },
574
-
575
- /**
576
- * Wait to connect, in case the remote kernel still starting up.
577
- */
578
- waitToConnect: async () => {
579
- if (isStaticNotebook()) {
580
- return;
421
+ waitUntilReady: async () => {
422
+ if (!isStaticNotebook() && !isWasm() && !runtimeManager.isSameOrigin) {
423
+ await runtimeManager.waitForHealthy();
581
424
  }
582
- if (isWasm()) {
583
- return;
584
- }
585
-
586
- if (runtimeManager.isSameOrigin) {
587
- return;
588
- }
589
- await runtimeManager.waitForHealthy();
590
425
  },
591
-
592
- /**
593
- * Handle messages sent by the kernel.
594
- */
595
- onMessage: (e) => {
426
+ checkHealth: () => runtimeManager.reconcileFromHealth(),
427
+ onNotification: (notification) => {
596
428
  try {
597
- handleMessage(e);
429
+ handleNotification(notification);
598
430
  } catch (error) {
599
- Logger.error("Failed to handle message", e.data, error);
600
- toast({
601
- title: "Failed to handle message",
602
- description: prettyError(error),
603
- variant: "danger",
604
- });
431
+ Logger.error("Failed to handle notification", notification, error);
432
+ onMessageError(error);
605
433
  }
606
434
  },
435
+ });
607
436
 
608
- /**
609
- * Handle a close event. We may want to reconnect.
610
- */
611
- onClose: (e) => {
612
- Logger.warn("WebSocket closed", e.code, e.reason);
613
- setAttachments([]);
614
- const decision = classifyCloseEvent(e);
437
+ // Subscribe before passive connection setup: static/WASM transports can emit synchronously.
438
+ useLayoutEffect(() => {
439
+ const updateConnection = () => {
440
+ const status = store.get(notebook.connectionAtom);
441
+ if (status.state === ConnectionState.NOT_STARTED) {
442
+ return;
443
+ }
444
+ if (status.state === ConnectionState.OPEN) {
445
+ updateStartupProgress(null);
446
+ return; // kernel-ready, not the socket, establishes session readiness.
447
+ }
448
+ if (status.state === ConnectionState.CONNECTING) {
449
+ setKernelStartupError(null);
450
+ }
615
451
  setConnection((previous) => {
616
- const status = decision.status;
617
452
  if (
618
453
  status.state === ConnectionState.CLOSED &&
619
454
  status.code === WebSocketClosedReason.KERNEL_STARTUP_ERROR &&
@@ -632,32 +467,17 @@ export function useMarimoKernelConnection(opts: {
632
467
  }
633
468
  return status;
634
469
  });
635
- if (decision.kind === "terminal" && decision.closeTransport) {
636
- ws.close(); // close to prevent reconnecting
637
- return;
638
- }
639
- if (decision.kind === "retry") {
640
- // Session should be valid
641
- // - browser tab might have been closed or re-opened
642
- // - computer might have just woken from sleep
643
- tryReconnecting(e.code, e.reason);
644
- }
645
- },
646
-
647
- /**
648
- * When we encounter an error, we should close the connection.
649
- */
650
- onError: (e) => {
651
- Logger.warn("WebSocket error", e);
652
- setAttachments([]);
653
- setConnection({
654
- state: ConnectionState.CLOSED,
655
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
656
- reason: "kernel not found",
657
- });
658
- tryReconnecting();
659
- },
660
- });
661
-
662
- return { connection, reconnect };
470
+ };
471
+ const unsubscribe = store.sub(notebook.connectionAtom, updateConnection);
472
+ updateConnection();
473
+ return unsubscribe;
474
+ }, [
475
+ notebook,
476
+ store,
477
+ setConnection,
478
+ setKernelStartupError,
479
+ updateStartupProgress,
480
+ ]);
481
+
482
+ return { notebook, connection, reconnect: notebook.reconnect };
663
483
  }
@@ -75,6 +75,9 @@
75
75
  --ring: hsl(215deg 20.2% 65.1%);
76
76
 
77
77
  /* Semantic colors. */
78
+ --brand: light-dark(#365f43, #a8c9ae);
79
+ --brand-hover: light-dark(#2b5037, #bdd8c2);
80
+ --brand-foreground: light-dark(#fff, #172b1c);
78
81
  --destructive: var(--red-9, #e5484d);
79
82
  --destructive-hover: var(--red-10, #dc3e42);
80
83
  --destructive-border: var(--red-11, #ce2c31);
@@ -89,6 +92,7 @@
89
92
  --action-hover: var(--yellow-10, #ffdc00);
90
93
  --action-border: var(--yellow-11, #9e6c00);
91
94
  --action-foreground: var(--yellow-12, #473b1f);
95
+ --search-highlight: var(--yellow-4, #fff394);
92
96
  --link: light-dark(hsl(211deg 90% 42%), hsl(211deg 90% 62%));
93
97
  --link-visited: light-dark(hsl(272deg 51% 54%), hsl(272deg 51% 74%));
94
98
  --stale: hsl(42deg 56% 44% / 25%);
@@ -137,6 +141,7 @@
137
141
  --action-hover: var(--yellow-7, #665417);
138
142
  --action-border: var(--yellow-11, #f5e147);
139
143
  --action-foreground: var(--yellow-12, #f6eeb4);
144
+ --search-highlight: var(--yellow-6, #524202);
140
145
  --stale: hsl(54deg 100% 86.7% / 25%);
141
146
  }
142
147
  }
@@ -0,0 +1,148 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import { act, render, renderHook, waitFor } from "@testing-library/react";
3
+ import { Activity } from "react";
4
+ import { describe, expect, it, vi } from "vitest";
5
+ import { Deferred } from "@/utils/Deferred";
6
+ import { useAsyncData } from "../useAsyncData";
7
+
8
+ function AsyncDataStatus({ fetch }: { fetch: () => Promise<string> }) {
9
+ const result = useAsyncData(fetch, []);
10
+ return (
11
+ <output
12
+ data-status={result.status}
13
+ data-fetching={result.isFetching}
14
+ data-error={result.error?.message ?? ""}
15
+ >
16
+ {result.data}
17
+ </output>
18
+ );
19
+ }
20
+
21
+ describe("useAsyncData", () => {
22
+ it.each(["success", "error"] as const)(
23
+ "restarts fetching when Activity reopens after %s",
24
+ async (initialStatus) => {
25
+ const initial = new Deferred<string>();
26
+ const restarted = new Deferred<string>();
27
+ const fetch = vi
28
+ .fn(() => restarted.promise)
29
+ .mockImplementationOnce(() => initial.promise);
30
+ const view = render(
31
+ <Activity mode="visible">
32
+ <AsyncDataStatus fetch={fetch} />
33
+ </Activity>,
34
+ );
35
+ await act(async () => {
36
+ if (initialStatus === "success") {
37
+ initial.resolve("first");
38
+ } else {
39
+ initial.reject(new Error("offline"));
40
+ }
41
+ });
42
+ expect(view.getByRole("status").dataset.status).toBe(initialStatus);
43
+
44
+ view.rerender(
45
+ <Activity mode="hidden">
46
+ <AsyncDataStatus fetch={fetch} />
47
+ </Activity>,
48
+ );
49
+ view.rerender(
50
+ <Activity mode="visible">
51
+ <AsyncDataStatus fetch={fetch} />
52
+ </Activity>,
53
+ );
54
+
55
+ expect(fetch).toHaveBeenCalledTimes(2);
56
+ expect(view.getByRole("status").dataset).toMatchObject({
57
+ status: initialStatus === "success" ? "loading" : "pending",
58
+ error: "",
59
+ fetching: "true",
60
+ });
61
+ expect(view.getByRole("status").textContent).toBe(
62
+ initialStatus === "success" ? "first" : "",
63
+ );
64
+ await act(async () => restarted.resolve("latest"));
65
+ expect(view.getByRole("status").dataset).toMatchObject({
66
+ status: "success",
67
+ fetching: "false",
68
+ });
69
+ expect(view.getByRole("status").textContent).toBe("latest");
70
+ },
71
+ );
72
+
73
+ it("ignores a request canceled when Activity hides", async () => {
74
+ const canceled = new Deferred<string>();
75
+ const restarted = new Deferred<string>();
76
+ const fetch = vi
77
+ .fn(() => restarted.promise)
78
+ .mockImplementationOnce(() => canceled.promise);
79
+ const view = render(
80
+ <Activity mode="visible">
81
+ <AsyncDataStatus fetch={fetch} />
82
+ </Activity>,
83
+ );
84
+ view.rerender(
85
+ <Activity mode="hidden">
86
+ <AsyncDataStatus fetch={fetch} />
87
+ </Activity>,
88
+ );
89
+ view.rerender(
90
+ <Activity mode="visible">
91
+ <AsyncDataStatus fetch={fetch} />
92
+ </Activity>,
93
+ );
94
+ expect(fetch).toHaveBeenCalledTimes(2);
95
+ await act(async () => canceled.resolve("obsolete"));
96
+ expect(view.getByRole("status").dataset.status).toBe("pending");
97
+ expect(view.getByRole("status").textContent).toBe("");
98
+ await act(async () => restarted.resolve("latest"));
99
+ expect(view.getByRole("status").dataset.status).toBe("success");
100
+ expect(view.getByRole("status").textContent).toBe("latest");
101
+ });
102
+
103
+ it("preserves data while refetching and ignores obsolete requests", async () => {
104
+ const requests = [
105
+ new Deferred<string>(),
106
+ new Deferred<string>(),
107
+ new Deferred<string>(),
108
+ ];
109
+ const fetch = vi
110
+ .fn(() => requests[0].promise)
111
+ .mockImplementationOnce(() => requests[0].promise)
112
+ .mockImplementationOnce(() => requests[1].promise)
113
+ .mockImplementationOnce(() => requests[2].promise);
114
+ const { result, rerender } = renderHook(
115
+ ({ key }) => useAsyncData(fetch, [key]),
116
+ { initialProps: { key: 0 } },
117
+ );
118
+ expect(result.current.status).toBe("pending");
119
+ await act(async () => requests[0].resolve("first"));
120
+ expect(result.current.data).toBe("first");
121
+ rerender({ key: 1 });
122
+ expect(result.current).toMatchObject({ status: "loading", data: "first" });
123
+ rerender({ key: 2 });
124
+ await act(async () => requests[1].resolve("obsolete"));
125
+ expect(result.current).toMatchObject({ status: "loading", data: "first" });
126
+ await act(async () => requests[2].resolve("latest"));
127
+ expect(result.current).toMatchObject({ status: "success", data: "latest" });
128
+ });
129
+
130
+ it("retries after errors and allows manual updates during a request", async () => {
131
+ const request = new Deferred<string>();
132
+ const fetch = vi
133
+ .fn<() => Promise<string>>()
134
+ .mockRejectedValueOnce(new Error("offline"))
135
+ .mockImplementation(() => request.promise);
136
+ const { result } = renderHook(() => useAsyncData(fetch, []));
137
+ await waitFor(() => expect(result.current.status).toBe("error"));
138
+ act(() => result.current.refetch());
139
+ expect(result.current.status).toBe("pending");
140
+ act(() => result.current.setData("manual"));
141
+ expect(result.current).toMatchObject({ status: "success", data: "manual" });
142
+ await act(async () => request.resolve("response"));
143
+ expect(result.current).toMatchObject({
144
+ status: "success",
145
+ data: "response",
146
+ });
147
+ });
148
+ });
@@ -121,6 +121,57 @@ describe("useDebounceControlledState", () => {
121
121
 
122
122
  expect(onChange).not.toHaveBeenCalled();
123
123
  });
124
+
125
+ it("replaces an uncommitted draft when the external value changes", () => {
126
+ const onChange = vi.fn();
127
+ const { result, rerender } = renderHook(
128
+ ({ value }) =>
129
+ useDebounceControlledState({
130
+ initialValue: value,
131
+ onChange,
132
+ delay: 1000,
133
+ }),
134
+ { initialProps: { value: "initial" } },
135
+ );
136
+
137
+ act(() => result.current.onChange("draft"));
138
+ rerender({ value: "external" });
139
+ expect(result.current.value).toBe("external");
140
+ act(() => vi.runAllTimers());
141
+ expect(result.current.debouncedValue).toBe("external");
142
+ expect(onChange).not.toHaveBeenCalled();
143
+ });
144
+
145
+ it("handles NaN external values without repeatedly resetting the draft", () => {
146
+ const onChange = vi.fn();
147
+ const { result, rerender } = renderHook(
148
+ ({ value }) =>
149
+ useDebounceControlledState({ initialValue: value, onChange }),
150
+ { initialProps: { value: Number.NaN } },
151
+ );
152
+
153
+ expect(result.current.value).toBeNaN();
154
+ act(() => result.current.onChange(1));
155
+ rerender({ value: Number.NaN });
156
+ expect(result.current.value).toBe(1);
157
+ rerender({ value: 2 });
158
+ expect(result.current.value).toBe(2);
159
+ rerender({ value: Number.NaN });
160
+ expect(result.current.value).toBeNaN();
161
+ });
162
+
163
+ it("resets the draft when the external value changes from zero to negative zero", () => {
164
+ const onChange = vi.fn();
165
+ const { result, rerender } = renderHook(
166
+ ({ value }) =>
167
+ useDebounceControlledState({ initialValue: value, onChange }),
168
+ { initialProps: { value: 0 } },
169
+ );
170
+
171
+ act(() => result.current.onChange(1));
172
+ rerender({ value: -0 });
173
+ expect(result.current.value).toBe(-0);
174
+ });
124
175
  });
125
176
 
126
177
  describe("useDebouncedCallback", () => {
@@ -0,0 +1,39 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import { renderHook } from "@testing-library/react";
3
+ import { describe, expect, it } from "vitest";
4
+ import { useDeepCompareMemoize } from "../useDeepCompareMemoize";
5
+
6
+ describe("useDeepCompareMemoize", () => {
7
+ it("keeps equal values stable and updates changed values immediately", () => {
8
+ const initial = { nested: [1, 2] };
9
+ const { result, rerender } = renderHook(
10
+ ({ value }) => useDeepCompareMemoize(value),
11
+ {
12
+ initialProps: { value: initial },
13
+ },
14
+ );
15
+ rerender({ value: { nested: [1, 2] } });
16
+ expect(result.current).toBe(initial);
17
+ const changed = { nested: [1, 3] };
18
+ rerender({ value: changed });
19
+ expect(result.current).toBe(changed);
20
+ rerender({ value: { nested: [1, 3] } });
21
+ expect(result.current).toBe(changed);
22
+ });
23
+
24
+ it("handles inline callbacks without causing additional renders", () => {
25
+ let renders = 0;
26
+ const { result, rerender } = renderHook(
27
+ ({ value }) => {
28
+ renders++;
29
+ return useDeepCompareMemoize({ callback: () => value });
30
+ },
31
+ { initialProps: { value: "initial" } },
32
+ );
33
+
34
+ const initialRenders = renders;
35
+ rerender({ value: "changed" });
36
+ expect(result.current.callback()).toBe("changed");
37
+ expect(renders).toBe(initialRenders + 1);
38
+ });
39
+ });
@@ -52,4 +52,32 @@ describe("useDelayElapsed", () => {
52
52
  // The pending timeout must be cleared, not left dangling.
53
53
  expect(vi.getTimerCount()).toBe(0);
54
54
  });
55
+
56
+ it("handles a NaN delay without resetting on every render", () => {
57
+ const { result, rerender } = renderHook(() => useDelayElapsed(Number.NaN));
58
+ expect(result.current).toBe(false);
59
+
60
+ act(() => vi.runAllTimers());
61
+ expect(result.current).toBe(true);
62
+ rerender();
63
+ expect(result.current).toBe(true);
64
+ expect(vi.getTimerCount()).toBe(0);
65
+ });
66
+
67
+ it("restarts a completed delay after the gate is temporarily disabled", () => {
68
+ const { result, rerender } = renderHook(
69
+ ({ delay }) => useDelayElapsed(delay),
70
+ { initialProps: { delay: 1000 } },
71
+ );
72
+ act(() => vi.advanceTimersByTime(1000));
73
+ expect(result.current).toBe(true);
74
+ rerender({ delay: 0 });
75
+ expect(result.current).toBe(true);
76
+ rerender({ delay: 1000 });
77
+ expect(result.current).toBe(false);
78
+ act(() => vi.advanceTimersByTime(999));
79
+ expect(result.current).toBe(false);
80
+ act(() => vi.advanceTimersByTime(1));
81
+ expect(result.current).toBe(true);
82
+ });
55
83
  });