@marimo-team/frontend 0.25.3-dev4 → 0.25.3-dev40

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 (288) hide show
  1. package/dist/assets/CellStatus-BGIWF94J.js +1 -0
  2. package/dist/assets/{ConnectedDataExplorerComponent-t8fH_TNc.js → ConnectedDataExplorerComponent-D9UK8o9A.js} +1 -1
  3. package/dist/assets/{ErrorBoundary-Gl5nCA8C.js → ErrorBoundary-hliG40Pk.js} +2 -2
  4. package/dist/assets/JsonOutput-9tmC-p6h.js +11 -0
  5. package/dist/assets/{LazyAnyLanguageCodeMirror-CKcCbG2G.js → LazyAnyLanguageCodeMirror-BPYE76jR.js} +2 -2
  6. package/dist/assets/MarimoErrorOutput-BaHdbtck.js +10 -0
  7. package/dist/assets/{RenderHTML-BbOqmHy3.js → RenderHTML-B1FzB5NY.js} +1 -1
  8. package/dist/assets/{RunButton-CK3LhnP5.js → RunButton-DpGokdF1.js} +1 -1
  9. package/dist/assets/{add-cell-with-ai-8UBC0xwR.js → add-cell-with-ai-BYWG0iKO.js} +13 -13
  10. package/dist/assets/{add-connection-dialog-DXQnd6eB.js → add-connection-dialog-BWbUVPJk.js} +2 -2
  11. package/dist/assets/{agent-panel-DdkVL-Kx.js → agent-panel-DKNiQrAl.js} +5 -5
  12. package/dist/assets/ai-model-dropdown-CIg9eu47.js +13 -0
  13. package/dist/assets/{alert-dialog-BaPGzeOK.js → alert-dialog-C-sWmKva.js} +1 -1
  14. package/dist/assets/any-language-editor-DUMe1UCk.js +1 -0
  15. package/dist/assets/{app-config-button-BKEG4x9u.js → app-config-button-BvkL2eSY.js} +1 -1
  16. package/dist/assets/arrays-0R2reF2y.js +1 -0
  17. package/dist/assets/{blob-D5nfNHOo.js → blob-BQfLiwKr.js} +1 -1
  18. package/dist/assets/{button-COI-NGrx.js → button-D8gVKwe-.js} +1 -1
  19. package/dist/assets/{cache-panel-DTY1dqvi.js → cache-panel-Byolv2ht.js} +1 -1
  20. package/dist/assets/{capabilities-BJvf-MD4.js → capabilities-Pq1YNPlK.js} +1 -1
  21. package/dist/assets/{cell-editor-32lVhib7.js → cell-editor-BKKUv4d5.js} +9 -9
  22. package/dist/assets/{cell-link-LSth_8hT.js → cell-link-B_QkRkm-.js} +1 -1
  23. package/dist/assets/{cells-BRkCaSsh.js → cells-CdH98VSn.js} +31 -31
  24. package/dist/assets/{chat-display-Dnagy4Nv.js → chat-display-CNbRSebo.js} +1 -1
  25. package/dist/assets/{chat-panel-DN9vsbgS.js → chat-panel-HbEKDf0c.js} +2 -2
  26. package/dist/assets/{chat-ui-Df8Vcj6n.js → chat-ui-BwMev8Bf.js} +1 -1
  27. package/dist/assets/{circle-DXf-VWZG.js → circle-B6D_6D8w.js} +1 -1
  28. package/dist/assets/code-visibility-sQ37CBNo.js +1 -0
  29. package/dist/assets/coerce-DWjFeXOY.js +1 -0
  30. package/dist/assets/{column-preview-CHK_DNrs.js → column-preview-CsEBzIUE.js} +1 -1
  31. package/dist/assets/command-palette-BzZ86pUz.js +1 -0
  32. package/dist/assets/{common-Cu6CDbuw.js → common-S2v8dsEy.js} +1 -1
  33. package/dist/assets/{components-CGw16R_N.js → components-D-TnFWjo.js} +1 -1
  34. package/dist/assets/{components-CP_UoFtT.js → components-DrU8dOzy.js} +1 -1
  35. package/dist/assets/{components-CXfO-F47.js → components-lDm0qi2t.js} +1 -1
  36. package/dist/assets/config-BoBwH7CU.js +1 -0
  37. package/dist/assets/config-CYsbZpwa.js +104 -0
  38. package/dist/assets/config-DGX7JYor.js +1 -0
  39. package/dist/assets/{copy-D8O_CYCG.js → copy-KlhazlhB.js} +1 -1
  40. package/dist/assets/{copy-icon-BIiRXh_3.js → copy-icon-8-a5vovc.js} +1 -1
  41. package/dist/assets/{createReducer-DgRODTW6.js → createReducer-HKXUbqyz.js} +1 -1
  42. package/dist/assets/{datasource-Cq6x_5ML.js → datasource-BA7YF4eo.js} +1 -1
  43. package/dist/assets/{dates-CfmdTL6B.js → dates-CQMwFko2.js} +1 -1
  44. package/dist/assets/{dependency-graph-panel--Kgtktcw.js → dependency-graph-panel-DsjI61Qa.js} +1 -1
  45. package/dist/assets/{dist-iaThVDkA.js → dist-jlhMTKo_.js} +1 -1
  46. package/dist/assets/{documentation-panel-BHmbeznw.js → documentation-panel-hk3IdeXy.js} +1 -1
  47. package/dist/assets/{download-DCJXy66H.js → download-CCZkq0y4.js} +1 -1
  48. package/dist/assets/{dropdown-menu-C1rnl-0o.js → dropdown-menu-DS6umAnH.js} +1 -1
  49. package/dist/assets/edit-page-BgS6tjth.js +9 -0
  50. package/dist/assets/{error-banner-Cd69vvyX.js → error-banner-B5kzVf_0.js} +1 -1
  51. package/dist/assets/{error-entries-Dk3VZ-OK.js → error-entries-BVXrGOTZ.js} +1 -1
  52. package/dist/assets/{error-panel-BPRYmC5J.js → error-panel-DYethUUR.js} +1 -1
  53. package/dist/assets/{errors-DCrsa1nH.js → errors-CNsgAw6m.js} +1 -1
  54. package/dist/assets/{es-Cqst59WX.js → es-N9SHpmG7.js} +1 -1
  55. package/dist/assets/feedback-button-DDzObtF2.js +1 -0
  56. package/dist/assets/{feedback-button-Bik2-e33.js → feedback-button-DaNc3uJa.js} +2 -2
  57. package/dist/assets/{file-explorer-panel-BmekFu7T.js → file-explorer-panel-CqnzSSYc.js} +1 -1
  58. package/dist/assets/{file-name-input-Cdc-TVqO.js → file-name-input-D4AvwZV9.js} +1 -1
  59. package/dist/assets/{floating-outline-XD9FAV-f.js → floating-outline-CTy-Iakk.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-Dm6mGwBi.js → gallery-page-ByjlvM6k.js} +1 -1
  64. package/dist/assets/{ganttDiagram-EL5Y4UJY-2n7Ig5Jm.js → ganttDiagram-EL5Y4UJY-CYqkCfFG.js} +1 -1
  65. package/dist/assets/{glide-data-editor-J9CAb70d.js → glide-data-editor-CABBj2b0.js} +2 -2
  66. package/dist/assets/{glide-utils-CIrAxdjv.js → glide-utils-HFqhvWHI.js} +2 -2
  67. package/dist/assets/{home-page-s3LUizSn.js → home-page-DejRSjeJ.js} +3 -3
  68. package/dist/assets/hooks-BJgCcVXZ.js +1 -0
  69. package/dist/assets/{html-to-image-DK9Zl1lU.js → html-to-image-D_0rd3_B.js} +1 -1
  70. package/dist/assets/index-ChfDZJzP.css +2 -0
  71. package/dist/assets/index-PDJQd7X6.js +30 -0
  72. package/dist/assets/{input-BoBxR50a.js → input-ByHt6aoV.js} +1 -1
  73. package/dist/assets/{kiosk-mode-BZECfg9k.js → kiosk-mode-BGmMSc7A.js} +1 -1
  74. package/dist/assets/{layout-B_GMOySr.js → layout-Ct0rjb-5.js} +5 -5
  75. package/dist/assets/linear-Bma8J3iM.js +1 -0
  76. package/dist/assets/links-CXcpNIM0.js +1 -0
  77. package/dist/assets/{logs-panel-ABM16htT.js → logs-panel-TyeqEvOY.js} +1 -1
  78. package/dist/assets/{markdown-renderer-BG3sp2rs.js → markdown-renderer-BFyD6Jhs.js} +1 -1
  79. package/dist/assets/{menu-items-BrwnTgyK.js → menu-items-BgUyNUpu.js} +1 -1
  80. package/dist/assets/{mermaid-a3xYymTy.js → mermaid-B6mqMm5_.js} +2 -2
  81. package/dist/assets/{name-cell-input-BgEBmqyL.js → name-cell-input-D-0VF3qd.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-DCXQ3_F6.js → outline-panel-DMTjk2WC.js} +1 -1
  86. package/dist/assets/{packages-panel-BMGOmZkR.js → packages-panel-DrT69k9L.js} +1 -1
  87. package/dist/assets/{panel-accordion-state-C1sdPp0m.js → panel-accordion-state-Ban6j---.js} +1 -1
  88. package/dist/assets/panels-BLYQRkEc.js +4 -0
  89. package/dist/assets/precisionRound-BxlUs8G4.js +1 -0
  90. package/dist/assets/process-output-DzpQYlpY.js +1 -0
  91. package/dist/assets/{quadrantDiagram-AXDQQJYC-yBhCDI7E.js → quadrantDiagram-AXDQQJYC-D9KH5Zge.js} +1 -1
  92. package/dist/assets/react-vega-BTNeboR2.js +189 -0
  93. package/dist/assets/{readonly-code-display-Do50PHI8.js → readonly-code-display-Cyn3k5TX.js} +1 -1
  94. package/dist/assets/{readonly-python-code-DCSMIsTX.js → readonly-python-code-DrXDDAnA.js} +1 -1
  95. package/dist/assets/{renderShortcut-BkVJQ_Zh.js → renderShortcut-H60uiHBp.js} +1 -1
  96. package/dist/assets/{reveal-component-CLE6vB1Z.js → reveal-component-_lKImPWP.js} +3 -3
  97. package/dist/assets/{run-page-Depd6lEE.js → run-page-7UNg--Rj.js} +1 -1
  98. package/dist/assets/{runs-BLp5jezv.js → runs-6lJxKV1w.js} +1 -1
  99. package/dist/assets/{runtime-details-state-CSPobEdR.js → runtime-details-state-CKOKhMhv.js} +1 -1
  100. package/dist/assets/{schemas-Dv42wLFq.js → schemas-ChRqMOTL.js} +1 -1
  101. package/dist/assets/{scratchpad-panel-lc6jmpjv.js → scratchpad-panel-C7h4MgqN.js} +1 -1
  102. package/dist/assets/{scroll-to-bottom-button-1FyowpWV.js → scroll-to-bottom-button-CIkON2WJ.js} +1 -1
  103. package/dist/assets/{secrets-panel-Bctbtkrp.js → secrets-panel-CmLZsNfp.js} +1 -1
  104. package/dist/assets/{select-DTBn5eTb.js → select-3CVGlYrC.js} +1 -1
  105. package/dist/assets/{session-DqoWmb7X.js → session-B8Y-GJnS.js} +1 -1
  106. package/dist/assets/session-panel-CLiWXc5y.js +1 -0
  107. package/dist/assets/{snippets-panel-Bh1oTw4m.js → snippets-panel-Wrqaorxa.js} +1 -1
  108. package/dist/assets/{spec-BfSBcO5B.js → spec-uCpVvaAR.js} +1 -1
  109. package/dist/assets/{state-uwRvJLb8.js → state-BY4_P6ko.js} +1 -1
  110. package/dist/assets/{state-BAJYRhmO.js → state-C0irhlOZ.js} +1 -1
  111. package/dist/assets/{state-DxQnC_1I.js → state-Ck7FMD5A.js} +1 -1
  112. package/dist/assets/{state-C3oDoyFe.js → state-DaWgt-rn.js} +1 -1
  113. package/dist/assets/state-TVREh4u4.js +3 -0
  114. package/dist/assets/{state-XsPO8hNi.js → state-_uXVmwuN.js} +1 -1
  115. package/dist/assets/{strings-B2vv-ENv.js → strings-C25fhMR_.js} +1 -1
  116. package/dist/assets/{swiper-component-a07domrZ.js → swiper-component-CRGRy5_O.js} +1 -1
  117. package/dist/assets/{switch-CTaiAjrz.js → switch-1h011wlH.js} +1 -1
  118. package/dist/assets/{tabs-BuQlZFbt.js → tabs-CY8E4r9U.js} +1 -1
  119. package/dist/assets/{terminal-Ck_vK-34.js → terminal-Dp5gyJ7l.js} +3 -3
  120. package/dist/assets/{time-MgToa8VM.js → time-DswSXBDk.js} +1 -1
  121. package/dist/assets/{toast-BGzLGnvw.js → toast-Gzxi-le_.js} +1 -1
  122. package/dist/assets/tooltip-Brya4Ghn.js +61 -0
  123. package/dist/assets/{tooltip-C9TuY18o.js → tooltip-CkGm8X7m.js} +1 -1
  124. package/dist/assets/tracing-fUNLyKuj.js +1 -0
  125. package/dist/assets/{tracing-panel-PeRXHD8_.js → tracing-panel-Cx4OqOoP.js} +2 -2
  126. package/dist/assets/{tree-actions-GM5E_bUs.js → tree-actions-B1vsdenY.js} +1 -1
  127. package/dist/assets/{use-restore-focus-BxEGwIbk.js → use-restore-focus-DdS7Tl0F.js} +1 -1
  128. package/dist/assets/{useAddCell-GR7eEQrT.js → useAddCell-DZ-50qEG.js} +1 -1
  129. package/dist/assets/useAsyncData-DhhdOGGd.js +1 -0
  130. package/dist/assets/{useBoolean-BDD6_xQQ.js → useBoolean-5g-bBaYF.js} +1 -1
  131. package/dist/assets/{useCellActionButton-B-9eG5z_.js → useCellActionButton-DINnRstv.js} +1 -1
  132. package/dist/assets/{useDeleteCell-2_bMClHd.js → useDeleteCell-CREJXVNp.js} +1 -1
  133. package/dist/assets/{useDependencyPanelTab-k3C3M18S.js → useDependencyPanelTab-Cw5VT16Q.js} +1 -1
  134. package/dist/assets/{useEventListener-D9cftcHj.js → useEventListener-CjDeLqFC.js} +1 -1
  135. package/dist/assets/{useIframeCapabilities-CwTkczVN.js → useIframeCapabilities-Czr9e4uG.js} +1 -1
  136. package/dist/assets/{useInstallPackage-iX1eufdl.js → useInstallPackage-Cw0dVELh.js} +1 -1
  137. package/dist/assets/{useInterval--x3s1VpI.js → useInterval-DJdQmXnq.js} +1 -1
  138. package/dist/assets/useNotebookActions-B4av4mLc.js +1 -0
  139. package/dist/assets/{useRunCells-B2vUhREg.js → useRunCells-BHBMk9zW.js} +1 -1
  140. package/dist/assets/{useSplitCell-DEb03htp.js → useSplitCell-DAgwZV0q.js} +1 -1
  141. package/dist/assets/{useTheme-COhSHkCj.js → useTheme-DLtPc-39.js} +1 -1
  142. package/dist/assets/{utils-CaQYvazl.js → utils-DfElfBpz.js} +1 -1
  143. package/dist/assets/vega-component-pQE2mKc1.js +1 -0
  144. package/dist/assets/{vega-embed-container-DJPNkHPb.js → vega-embed-container-DtArSJz1.js} +1 -1
  145. package/dist/assets/vega-loader.browser-Bu25TKax.js +6 -0
  146. package/dist/assets/{write-secret-modal-DzKAInL2.js → write-secret-modal-usRSi1bb.js} +1 -1
  147. package/dist/assets/{xychartDiagram-S5SC5T6Z-KbGzG6-u.js → xychartDiagram-S5SC5T6Z-CeRnBS5e.js} +1 -1
  148. package/dist/index.html +76 -77
  149. package/package.json +4 -2
  150. package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
  151. package/src/__tests__/mount.test.ts +28 -8
  152. package/src/components/app-config/ai-config.tsx +5 -1
  153. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  154. package/src/components/data-table/__tests__/export-actions.test.tsx +329 -6
  155. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  156. package/src/components/data-table/columns.tsx +2 -1
  157. package/src/components/data-table/date-popover.tsx +6 -4
  158. package/src/components/data-table/export-actions.tsx +192 -116
  159. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +2 -2
  160. package/src/components/data-table/schemas.ts +14 -36
  161. package/src/components/editor/Output.tsx +9 -2
  162. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  163. package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +80 -252
  164. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +186 -34
  165. package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
  166. package/src/components/editor/actions/pair-with-agent-modal.tsx +96 -36
  167. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  168. package/src/components/editor/chrome/panels/error-panel.tsx +1 -0
  169. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +366 -0
  170. package/src/components/editor/chrome/wrapper/footer-item.tsx +32 -24
  171. package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +237 -0
  172. package/src/components/editor/chrome/wrapper/footer.tsx +2 -0
  173. package/src/components/editor/connections/components.tsx +1 -1
  174. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  175. package/src/components/editor/controls/command-palette.tsx +22 -10
  176. package/src/components/editor/dynamic-favicon.tsx +21 -40
  177. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +537 -0
  178. package/src/components/editor/errors/send-error-report-button.tsx +69 -0
  179. package/src/components/editor/output/MarimoErrorOutput.tsx +21 -5
  180. package/src/components/editor/output/MarimoTracebackOutput.tsx +36 -21
  181. package/src/components/editor/output/__tests__/ansi-reduce.test.ts +18 -0
  182. package/src/components/editor/output/ansi-reduce.ts +6 -0
  183. package/src/components/pages/home-page.tsx +4 -4
  184. package/src/components/tracing/tracing.tsx +2 -2
  185. package/src/core/attachments/__tests__/toast.test.tsx +58 -0
  186. package/src/core/attachments/state.ts +18 -0
  187. package/src/core/attachments/toast.ts +15 -0
  188. package/src/core/cells/__tests__/cells.test.ts +52 -0
  189. package/src/core/cells/__tests__/error-report.test.ts +139 -0
  190. package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
  191. package/src/core/cells/__tests__/session.test.ts +50 -1
  192. package/src/core/cells/cells.ts +2 -1
  193. package/src/core/cells/error-report.ts +63 -0
  194. package/src/core/cells/pending-delete-service.ts +2 -6
  195. package/src/core/cells/session.ts +28 -1
  196. package/src/core/config/pair.ts +5 -3
  197. package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
  198. package/src/core/datasets/data-source-discovery.ts +4 -6
  199. package/src/core/edit-app.tsx +2 -2
  200. package/src/core/host/AGENTS.md +5 -0
  201. package/src/core/host/__tests__/attachments.test.ts +57 -0
  202. package/src/core/host/__tests__/document.test.ts +124 -0
  203. package/src/core/host/__tests__/events.test.ts +46 -0
  204. package/src/core/host/__tests__/fixtures.ts +39 -0
  205. package/src/core/host/__tests__/notebook-client.test.ts +433 -0
  206. package/src/core/host/__tests__/protocol.test.ts +15 -0
  207. package/src/core/host/__tests__/stream.test.ts +209 -0
  208. package/src/core/host/connection.ts +81 -0
  209. package/src/core/host/events.ts +61 -0
  210. package/src/core/host/notebook-client.ts +290 -0
  211. package/src/core/host/protocol.ts +11 -6
  212. package/src/core/host/resources/attachments.ts +19 -0
  213. package/src/core/host/resources/document.ts +101 -0
  214. package/src/core/host/resources/resource.ts +47 -0
  215. package/src/core/host/stream.ts +211 -0
  216. package/src/core/hotkeys/actions.ts +2 -4
  217. package/src/core/network/connection.ts +4 -2
  218. package/src/core/run-app.tsx +2 -2
  219. package/src/core/runtime/__tests__/runtime.test.ts +55 -11
  220. package/src/core/runtime/runtime.ts +18 -5
  221. package/src/core/runtime/types.ts +2 -0
  222. package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -17
  223. package/src/core/websocket/__tests__/{useWebSocket.test.ts → createConnectionTransport.test.ts} +1 -1
  224. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +170 -0
  225. package/src/core/websocket/__tests__/{useMarimoKernelConnection.hook.test.tsx → useNotebookSession.hook.test.tsx} +181 -65
  226. package/src/core/websocket/connection-state.ts +70 -0
  227. package/src/core/websocket/state.ts +7 -0
  228. package/src/core/websocket/transports/__tests__/sse.test.ts +1 -4
  229. package/src/core/websocket/transports/create.ts +31 -0
  230. package/src/core/websocket/transports/transport.ts +12 -23
  231. package/src/core/websocket/types.ts +9 -39
  232. package/src/core/websocket/useNotebookClient.ts +88 -0
  233. package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +59 -225
  234. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  235. package/src/hooks/useAsyncData.ts +3 -3
  236. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  237. package/src/hooks/useElapsedTime.ts +3 -3
  238. package/src/mount.tsx +7 -3
  239. package/src/plugins/core/registerReactComponent.tsx +1 -1
  240. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  241. package/src/plugins/impl/FormPlugin.tsx +1 -1
  242. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +84 -0
  243. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  244. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  245. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  246. package/src/plugins/impl/vega/make-selectable.ts +3 -6
  247. package/src/utils/__tests__/arrays.test.ts +35 -0
  248. package/src/utils/__tests__/numbers.test.ts +48 -1
  249. package/src/utils/__tests__/objects.test.ts +21 -0
  250. package/src/utils/arrays.ts +10 -0
  251. package/src/utils/numbers.ts +28 -17
  252. package/src/utils/objects.ts +3 -7
  253. package/src/utils/once.ts +19 -19
  254. package/dist/assets/CellStatus-BP2mavAq.js +0 -1
  255. package/dist/assets/JsonOutput-DxHKOpPG.js +0 -11
  256. package/dist/assets/MarimoErrorOutput-D60XkCny.js +0 -6
  257. package/dist/assets/ai-model-dropdown-B0z6-zhp.js +0 -5
  258. package/dist/assets/any-language-editor-BLCAD3yx.js +0 -1
  259. package/dist/assets/api-BeC_VSGv.js +0 -1
  260. package/dist/assets/arrays-BqUx5USx.js +0 -1
  261. package/dist/assets/code-visibility-B-_GvwR5.js +0 -1
  262. package/dist/assets/coerce-CpE6kUdX.js +0 -1
  263. package/dist/assets/command-palette-Cf7WX-nr.js +0 -1
  264. package/dist/assets/config-BOKzA3tF.js +0 -1
  265. package/dist/assets/config-DX8Y2WbJ.js +0 -1
  266. package/dist/assets/config-VPSq73n8.js +0 -1
  267. package/dist/assets/edit-page-BT74CF4G.js +0 -9
  268. package/dist/assets/feedback-button-B2gKC5cn.js +0 -1
  269. package/dist/assets/hooks-RnjuFmEx.js +0 -1
  270. package/dist/assets/index-Bcdzex-3.css +0 -2
  271. package/dist/assets/index-D-kY3FU1.js +0 -29
  272. package/dist/assets/linear-0eij_aAG.js +0 -1
  273. package/dist/assets/links-DkkL9DVa.js +0 -1
  274. package/dist/assets/numbers-Mk3-CcoU.js +0 -1
  275. package/dist/assets/pair-CaQyK6En.js +0 -1
  276. package/dist/assets/panels-Dn59o8ay.js +0 -4
  277. package/dist/assets/precisionRound-Bq_nmf5u.js +0 -1
  278. package/dist/assets/process-output-D0KGq04R.js +0 -1
  279. package/dist/assets/react-vega-C3rYGtP6.js +0 -189
  280. package/dist/assets/session-panel-BxS5hiG2.js +0 -1
  281. package/dist/assets/state-_Qai_QyX.js +0 -3
  282. package/dist/assets/tooltip-mT3CGest.js +0 -61
  283. package/dist/assets/tracing-Di6pUmX8.js +0 -1
  284. package/dist/assets/useAsyncData-C7aJJz0Y.js +0 -1
  285. package/dist/assets/useNotebookActions-DIc4xTzY.js +0 -1
  286. package/dist/assets/vega-component-CnadTgoE.js +0 -1
  287. package/dist/assets/vega-loader.browser-CLRkNgm7.js +0 -6
  288. package/src/core/websocket/useWebSocket.tsx +0 -88
@@ -1,8 +1,8 @@
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
8
  import { getNotebook, useCellActions } from "@/core/cells/cells";
@@ -12,8 +12,7 @@ import type {
12
12
  NotificationMessageData,
13
13
  NotificationPayload,
14
14
  } from "@/core/kernel/messages";
15
- import { TRANSPORT_EXHAUSTED_REASON } from "@/core/websocket/transports/ws";
16
- import { useConnectionTransport } from "@/core/websocket/useWebSocket";
15
+ import { useNotebookClient } from "@/core/websocket/useNotebookClient";
17
16
  import { renderHTML } from "@/plugins/core/RenderHTML";
18
17
  import {
19
18
  handleWidgetMessage,
@@ -21,11 +20,7 @@ import {
21
20
  } from "@/plugins/impl/anywidget/registry";
22
21
  import { logNever } from "@/utils/assertNever";
23
22
  import { prettyError } from "@/utils/errors";
24
- import {
25
- type JsonString,
26
- safeExtractSetUIElementMessageBuffers,
27
- } from "@/utils/json/base64";
28
- import { jsonParseWithSpecialChar } from "@/utils/json/json-parser";
23
+ import { safeExtractSetUIElementMessageBuffers } from "@/utils/json/base64";
29
24
  import { Logger } from "@/utils/Logger";
30
25
  import { reloadSafe } from "@/utils/reload-safe";
31
26
  import { useAlertActions } from "../alerts/state";
@@ -70,7 +65,7 @@ import { type LayoutState, useLayoutActions } from "../layout/state";
70
65
  import { kioskModeAtom } from "../mode";
71
66
  import { connectionAtom, startupProgressAtom } from "../network/connection";
72
67
  import type { RequestId } from "../network/DeferredRequestRegistry";
73
- import { useRuntimeManager } from "../runtime/config";
68
+ import { runtimeConfigAtom, useRuntimeManager } from "../runtime/config";
74
69
  import { SECRETS_REGISTRY } from "../secrets/request-registry";
75
70
  import { isStaticNotebook } from "../static/static-state";
76
71
  import {
@@ -81,104 +76,10 @@ import { useStorageActions } from "../storage/state";
81
76
  import { useVariablesActions } from "../variables/state";
82
77
  import type { VariableName } from "../variables/types";
83
78
  import { isWasm } from "../wasm/utils";
84
- import {
85
- type ConnectionStatus,
86
- WebSocketClosedReason,
87
- ConnectionState,
88
- } from "./types";
79
+ import { WebSocketClosedReason, ConnectionState } from "./types";
89
80
 
90
81
  const SUPPORTS_LAZY_KERNELS = true;
91
82
 
92
- // All MARIMO_* reasons except TRANSPORT_EXHAUSTED are emitted by the backend
93
- // (marimo/_server/api/endpoints/ws_endpoint.py and ws/*.py). Keep in sync with
94
- // the backend literals. Over the SSE transport these arrive as in-band
95
- // `close` events rather than WebSocket close frames, so every terminal
96
- // reason must be classified here — an unclassified reason falls into the
97
- // retry path.
98
- export type CloseReason =
99
- | "MARIMO_NO_FILE_KEY"
100
- | "MARIMO_NO_SESSION_ID"
101
- | "MARIMO_NO_SESSION"
102
- | "MARIMO_SHUTDOWN"
103
- | "MARIMO_KERNEL_STARTUP_ERROR"
104
- | "MARIMO_UNAUTHORIZED"
105
- | "MARIMO_KIOSK_NOT_ALLOWED"
106
- | typeof TRANSPORT_EXHAUSTED_REASON;
107
-
108
- export type CloseDecision =
109
- | { kind: "terminal"; status: ConnectionStatus; closeTransport: boolean }
110
- | { kind: "gave-up"; status: ConnectionStatus }
111
- | { kind: "retry"; status: ConnectionStatus };
112
-
113
- export function classifyCloseEvent(event: { reason?: string }): CloseDecision {
114
- switch (event.reason as CloseReason | undefined) {
115
- case TRANSPORT_EXHAUSTED_REASON:
116
- return {
117
- kind: "gave-up",
118
- status: {
119
- state: ConnectionState.CLOSED,
120
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
121
- reason: "kernel not found",
122
- },
123
- };
124
- case "MARIMO_NO_FILE_KEY":
125
- case "MARIMO_NO_SESSION_ID":
126
- case "MARIMO_NO_SESSION":
127
- case "MARIMO_SHUTDOWN":
128
- return {
129
- kind: "terminal",
130
- status: {
131
- state: ConnectionState.CLOSED,
132
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
133
- reason: "kernel not found",
134
- },
135
- closeTransport: true,
136
- };
137
- case "MARIMO_UNAUTHORIZED":
138
- return {
139
- kind: "terminal",
140
- status: {
141
- state: ConnectionState.CLOSED,
142
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
143
- reason: "not authorized",
144
- },
145
- closeTransport: true,
146
- };
147
- case "MARIMO_KIOSK_NOT_ALLOWED":
148
- return {
149
- kind: "terminal",
150
- status: {
151
- state: ConnectionState.CLOSED,
152
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
153
- reason: "kiosk mode is not available for this session",
154
- },
155
- closeTransport: true,
156
- };
157
- case "MARIMO_KERNEL_STARTUP_ERROR":
158
- return {
159
- kind: "terminal",
160
- status: {
161
- state: ConnectionState.CLOSED,
162
- code: WebSocketClosedReason.KERNEL_STARTUP_ERROR,
163
- reason: "Failed to start kernel sandbox",
164
- },
165
- closeTransport: true,
166
- };
167
- default:
168
- // Empty/undefined reasons are normal transient closes. Anything else is
169
- // an unknown server reason; warn so a new MARIMO_* reason doesn't fall
170
- // silently into the retry path.
171
- if (event.reason) {
172
- logNever(event.reason as never);
173
- }
174
- }
175
-
176
- return {
177
- kind: "retry",
178
- status: { state: ConnectionState.CONNECTING },
179
- };
180
- }
181
-
182
83
  function getExistingCells(): CellData[] | undefined {
183
84
  if (!SUPPORTS_LAZY_KERNELS) {
184
85
  return undefined;
@@ -190,16 +91,12 @@ function getExistingCells(): CellData[] | undefined {
190
91
  );
191
92
  }
192
93
 
193
- /**
194
- * Creates a connection to the Marimo kernel and handles incoming messages.
195
- */
196
- export function useMarimoKernelConnection(opts: {
94
+ /** Coordinate session startup and apply notifications to the editor. */
95
+ export function useNotebookSession(opts: {
197
96
  sessionId: SessionId;
198
97
  autoInstantiate: boolean;
199
98
  setCells: (cells: CellData[], layout: LayoutState) => void;
200
99
  }) {
201
- // Track whether we want to try reconnecting.
202
- const shouldTryReconnecting = useRef<boolean>(true);
203
100
  const { autoInstantiate, sessionId, setCells } = opts;
204
101
  const { showBoundary } = useErrorBoundary();
205
102
 
@@ -238,6 +135,7 @@ export function useMarimoKernelConnection(opts: {
238
135
  const setStableSessionId = useSetAtom(stableSessionIdAtom);
239
136
  const setCapabilities = useSetAtom(capabilitiesAtom);
240
137
  const runtimeManager = useRuntimeManager();
138
+ const { notebookId } = useAtomValue(runtimeConfigAtom);
241
139
  const transportType = useAtomValue(connectionTransportTypeAtom);
242
140
  const setCacheInfo = useSetAtom(cacheInfoAtom);
243
141
  const setKernelStartupError = useSetAtom(kernelStartupErrorAtom);
@@ -246,8 +144,7 @@ export function useMarimoKernelConnection(opts: {
246
144
  filterFromVariables: filterStorageFromVariables,
247
145
  } = useStorageActions();
248
146
 
249
- const handleMessage = (e: MessageEvent<JsonString<NotificationPayload>>) => {
250
- const msg = jsonParseWithSpecialChar(e.data);
147
+ const handleNotification = (msg: NotificationPayload) => {
251
148
  switch (msg.data.op) {
252
149
  case "reload":
253
150
  reloadSafe();
@@ -505,107 +402,58 @@ export function useMarimoKernelConnection(opts: {
505
402
  }
506
403
  };
507
404
 
508
- const tryReconnecting = (code?: number, reason?: string) => {
509
- // If not properly gated, we could try reconnecting forever if the
510
- // issue is not transient. So we want to try reconnecting only once after an
511
- // open connection is closed.
512
- if (shouldTryReconnecting.current) {
513
- shouldTryReconnecting.current = false;
514
- ws.reconnect(code, reason);
515
- }
516
- };
517
-
518
- // Manual reconnect. Probes /health first to fail fast when the runtime
519
- // is unreachable, instead of waiting on partysocket's retry budget.
520
- const reconnect = async () => {
521
- if (
522
- ws.readyState === WebSocket.OPEN ||
523
- ws.readyState === WebSocket.CONNECTING
524
- ) {
525
- return;
526
- }
527
- shouldTryReconnecting.current = true;
528
- setKernelStartupError(null);
529
- setConnection({ state: ConnectionState.CONNECTING });
530
- const healthy = await runtimeManager.reconcileFromHealth();
531
- if (!healthy) {
532
- shouldTryReconnecting.current = false;
533
- setConnection({
534
- state: ConnectionState.CLOSED,
535
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
536
- reason: "kernel not found",
537
- });
538
- return;
539
- }
540
- ws.reconnect();
405
+ const store = useStore();
406
+ const onMessageError = (error: unknown) => {
407
+ toast({
408
+ title: "Failed to handle message",
409
+ description: prettyError(error),
410
+ variant: "danger",
411
+ });
541
412
  };
542
-
543
- const ws = useConnectionTransport({
413
+ const notebook = useNotebookClient({
414
+ sessionId,
544
415
  static: isStaticNotebook(),
545
416
  transportType,
546
- /**
547
- * Unique URL for this session.
548
- */
417
+ messageFormat:
418
+ notebookId && transportType === "websocket" ? "host" : "session",
549
419
  url: () =>
550
420
  transportType === "sse"
551
421
  ? runtimeManager.getSseURL(sessionId).toString()
552
422
  : runtimeManager.getWsURL(sessionId).toString(),
553
- /**
554
- * Auth headers for the SSE transport. WebSockets cannot send headers
555
- * and instead put the access token in the URL.
556
- */
423
+ // SSE uses headers for auth. WebSockets carry the token in the URL.
557
424
  headers: () => runtimeManager.headers(),
558
-
559
- /**
560
- * The transport is open; kernel-ready establishes session readiness.
561
- */
562
- onOpen: async () => {
563
- updateStartupProgress(null);
564
- // If we are open, we can reset our reconnecting flag.
565
- shouldTryReconnecting.current = true;
566
- },
567
-
568
- /**
569
- * Wait to connect, in case the remote kernel still starting up.
570
- */
571
- waitToConnect: async () => {
572
- if (isStaticNotebook()) {
573
- return;
574
- }
575
- if (isWasm()) {
576
- return;
577
- }
578
-
579
- if (runtimeManager.isSameOrigin) {
580
- return;
425
+ waitUntilReady: async () => {
426
+ if (!isStaticNotebook() && !isWasm() && !runtimeManager.isSameOrigin) {
427
+ await runtimeManager.waitForHealthy();
581
428
  }
582
- await runtimeManager.waitForHealthy();
583
429
  },
584
-
585
- /**
586
- * Handle messages sent by the kernel.
587
- */
588
- onMessage: (e) => {
430
+ checkHealth: () => runtimeManager.reconcileFromHealth(),
431
+ onMessageError,
432
+ onNotification: (notification) => {
589
433
  try {
590
- handleMessage(e);
434
+ handleNotification(notification);
591
435
  } catch (error) {
592
- Logger.error("Failed to handle message", e.data, error);
593
- toast({
594
- title: "Failed to handle message",
595
- description: prettyError(error),
596
- variant: "danger",
597
- });
436
+ Logger.error("Failed to handle notification", notification, error);
437
+ onMessageError(error);
598
438
  }
599
439
  },
440
+ });
600
441
 
601
- /**
602
- * Handle a close event. We may want to reconnect.
603
- */
604
- onClose: (e) => {
605
- Logger.warn("WebSocket closed", e.code, e.reason);
606
- const decision = classifyCloseEvent(e);
442
+ // Subscribe before passive connection setup: static/WASM transports can emit synchronously.
443
+ useLayoutEffect(() => {
444
+ const updateConnection = () => {
445
+ const status = store.get(notebook.connectionAtom);
446
+ if (status.state === ConnectionState.NOT_STARTED) {
447
+ return;
448
+ }
449
+ if (status.state === ConnectionState.OPEN) {
450
+ updateStartupProgress(null);
451
+ return; // kernel-ready, not the socket, establishes session readiness.
452
+ }
453
+ if (status.state === ConnectionState.CONNECTING) {
454
+ setKernelStartupError(null);
455
+ }
607
456
  setConnection((previous) => {
608
- const status = decision.status;
609
457
  if (
610
458
  status.state === ConnectionState.CLOSED &&
611
459
  status.code === WebSocketClosedReason.KERNEL_STARTUP_ERROR &&
@@ -624,31 +472,17 @@ export function useMarimoKernelConnection(opts: {
624
472
  }
625
473
  return status;
626
474
  });
627
- if (decision.kind === "terminal" && decision.closeTransport) {
628
- ws.close(); // close to prevent reconnecting
629
- return;
630
- }
631
- if (decision.kind === "retry") {
632
- // Session should be valid
633
- // - browser tab might have been closed or re-opened
634
- // - computer might have just woken from sleep
635
- tryReconnecting(e.code, e.reason);
636
- }
637
- },
638
-
639
- /**
640
- * When we encounter an error, we should close the connection.
641
- */
642
- onError: (e) => {
643
- Logger.warn("WebSocket error", e);
644
- setConnection({
645
- state: ConnectionState.CLOSED,
646
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
647
- reason: "kernel not found",
648
- });
649
- tryReconnecting();
650
- },
651
- });
652
-
653
- return { connection, reconnect };
475
+ };
476
+ const unsubscribe = store.sub(notebook.connectionAtom, updateConnection);
477
+ updateConnection();
478
+ return unsubscribe;
479
+ }, [
480
+ notebook,
481
+ store,
482
+ setConnection,
483
+ setKernelStartupError,
484
+ updateStartupProgress,
485
+ ]);
486
+
487
+ return { notebook, connection, reconnect: notebook.reconnect };
654
488
  }
@@ -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
+ });
@@ -322,7 +322,7 @@ export function useAsyncData<T>(
322
322
  const [nonce, setNonce] = useState(0);
323
323
  const [result, setResult] = useState<
324
324
  PendingResult<T> | LoadingResult<T> | ErrorResult<T> | SuccessResult<T>
325
- >(Result.pending());
325
+ >(() => Result.pending());
326
326
 
327
327
  const asProps =
328
328
  typeof loaderOrProps === "function"
@@ -339,12 +339,12 @@ export function useAsyncData<T>(
339
339
  keepPrevious = true;
340
340
  },
341
341
  };
342
+ // Activity can restart this effect without changing dependencies, so
343
+ // loading state must follow the request lifecycle.
342
344
  setResult((prevResult) => {
343
- // If we have previous data, show reloading state
344
345
  if (prevResult.status === "success" || prevResult.status === "loading") {
345
346
  return Result.loading(prevResult.data);
346
347
  }
347
- // Otherwise, show initial loading state
348
348
  return Result.pending();
349
349
  });
350
350
  fetchStable(context)
@@ -42,11 +42,13 @@ export function useDetectedDataSources(
42
42
  dialects: [...connections.values()].map((connection) =>
43
43
  connection.dialect.toLowerCase(),
44
44
  ),
45
- storageProtocols: storageNamespaces.map((namespace) =>
46
- namespace.protocol.toLowerCase(),
45
+ storageProtocols: new Set(
46
+ storageNamespaces.map((namespace) => namespace.protocol.toLowerCase()),
47
47
  ),
48
- storageBackendTypes: storageNamespaces.map((namespace) =>
49
- namespace.backendType.toLowerCase(),
48
+ storageBackendTypes: new Set(
49
+ storageNamespaces.map((namespace) =>
50
+ namespace.backendType.toLowerCase(),
51
+ ),
50
52
  ),
51
53
  };
52
54
 
@@ -1,13 +1,13 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import { useEffect, useRef, useState } from "react";
3
+ import { useEffect, useState } from "react";
4
4
  import { type Milliseconds, Time } from "@/utils/time";
5
5
 
6
6
  /**
7
7
  * Returns the elapsed time since mount, in milliseconds.
8
8
  */
9
9
  export function useElapsedTime(initialStartTimeMs: Milliseconds) {
10
- const startTime = useRef(initialStartTimeMs);
10
+ const [startTime] = useState(initialStartTimeMs);
11
11
  const [endTime, setEndTime] = useState(initialStartTimeMs);
12
12
 
13
13
  useEffect(() => {
@@ -21,5 +21,5 @@ export function useElapsedTime(initialStartTimeMs: Milliseconds) {
21
21
  return () => clearInterval(interval);
22
22
  }, []);
23
23
 
24
- return endTime - startTime.current;
24
+ return endTime - startTime;
25
25
  }
package/src/mount.tsx CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  userConfigAtom,
11
11
  } from "@/core/config/config";
12
12
  import { KnownQueryParams } from "@/core/constants";
13
- import { pairPreviewAtom, pairPreviewSchema } from "@/core/config/pair";
13
+ import { pairConfigAtom, pairConfigSchema } from "@/core/config/pair";
14
14
  import { getFilenameFromDOM } from "@/core/dom/htmlUtils";
15
15
  import { getMarimoCode } from "@/core/meta/globals";
16
16
  import {
@@ -209,7 +209,7 @@ const mountOptionsSchema = z.object({
209
209
  * marimo config overrides
210
210
  */
211
211
  configOverrides: passthroughObject,
212
- pairPreview: pairPreviewSchema.optional(),
212
+ pairConfig: pairConfigSchema.optional(),
213
213
  /**
214
214
  * marimo app config
215
215
  */
@@ -274,6 +274,8 @@ const mountOptionsSchema = z.object({
274
274
  .transform((val) => val as api.Notebook["NotebookV1"]),
275
275
  ]),
276
276
 
277
+ notebookId: z.string().optional(),
278
+
277
279
  /**
278
280
  * Runtime configs
279
281
  */
@@ -284,6 +286,7 @@ const mountOptionsSchema = z.object({
284
286
  // Lazy by default, but can be overridden by the runtime config
285
287
  lazy: z.boolean().default(true),
286
288
  authToken: z.string().nullish(),
289
+ notebookId: z.string().optional(),
287
290
  }),
288
291
  )
289
292
  .nullish()
@@ -347,7 +350,7 @@ function initStore(options: unknown) {
347
350
  );
348
351
  store.set(userConfigAtom, parseUserConfig(parsedOptions.data.config));
349
352
  store.set(appConfigAtom, parseAppConfig(parsedOptions.data.appConfig));
350
- store.set(pairPreviewAtom, parsedOptions.data.pairPreview);
353
+ store.set(pairConfigAtom, parsedOptions.data.pairConfig);
351
354
 
352
355
  // Runtime config
353
356
  if (parsedOptions.data.runtimeConfig.length > 0) {
@@ -364,6 +367,7 @@ function initStore(options: unknown) {
364
367
  } else {
365
368
  store.set(runtimeConfigAtom, {
366
369
  ...DEFAULT_RUNTIME_CONFIG,
370
+ notebookId: parsedOptions.data.notebookId,
367
371
  lazy: false,
368
372
  serverToken: parsedOptions.data.serverToken,
369
373
  });
@@ -104,7 +104,7 @@ function PluginSlotInternal<T>(
104
104
  ref: React.Ref<PluginSlotHandle>,
105
105
  ): JSX.Element {
106
106
  const [childNodes, setChildNodes] = useState<ReactNode>(children);
107
- const [value, setValue] = useState<T>(getInitialValue());
107
+ const [value, setValue] = useState<T>(getInitialValue);
108
108
  const { theme } = useTheme();
109
109
 
110
110
  const [parsedResult, setParsedResult] = useState(() => {
@@ -97,13 +97,9 @@ const Dropdown = (props: DropdownProps): JSX.Element => {
97
97
  id={id}
98
98
  disabled={disabled}
99
99
  >
100
- {allowSelectNone ? (
101
- <option value={EMPTY_VALUE} selected={value.length === 0}>
102
- --
103
- </option>
104
- ) : null}
100
+ {allowSelectNone ? <option value={EMPTY_VALUE}>--</option> : null}
105
101
  {options.map((option) => (
106
- <option value={option} selected={value.includes(option)} key={option}>
102
+ <option value={option} key={option}>
107
103
  {option}
108
104
  </option>
109
105
  ))}
@@ -212,7 +212,7 @@ const Form = ({
212
212
  // wrapped plugin; this buffered value is in contrast to the actual
213
213
  // value of the plugin, which is the value of the wrapped plugin when
214
214
  // the submit button was last clicked/activated.
215
- const [internalValue, setInternalValue] = useState<T>(
215
+ const [internalValue, setInternalValue] = useState<T>(() =>
216
216
  UI_ELEMENT_REGISTRY.lookupValue(elementId),
217
217
  );
218
218