@marimo-team/frontend 0.25.3-dev5 → 0.25.3-dev55

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 (343) hide show
  1. package/dist/assets/CellStatus-cD3S0wEA.js +1 -0
  2. package/dist/assets/{ConnectedDataExplorerComponent-t8fH_TNc.js → ConnectedDataExplorerComponent-D9UK8o9A.js} +1 -1
  3. package/dist/assets/{ErrorBoundary-B4n9EnHB.js → ErrorBoundary-DJhsFCsX.js} +2 -2
  4. package/dist/assets/JsonOutput-Pm6OCP3J.js +11 -0
  5. package/dist/assets/{LazyAnyLanguageCodeMirror-CKcCbG2G.js → LazyAnyLanguageCodeMirror-BPYE76jR.js} +2 -2
  6. package/dist/assets/MarimoErrorOutput-DlEjLCZ-.js +10 -0
  7. package/dist/assets/{RenderHTML-9bMgbYmo.js → RenderHTML-CfQ6i3QV.js} +1 -1
  8. package/dist/assets/RunButton-DvuvkgrG.js +1 -0
  9. package/dist/assets/{add-cell-with-ai-C_SruRWS.js → add-cell-with-ai-BhmuohAY.js} +7 -7
  10. package/dist/assets/{add-connection-dialog-CoE_PhuD.js → add-connection-dialog-D7EYSraq.js} +2 -2
  11. package/dist/assets/{agent-panel-0b3k4QzJ.js → agent-panel-GFhlWVub.js} +3 -3
  12. package/dist/assets/ai-model-dropdown-YT1HiXiK.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-iXdCWvfC.js → app-config-button-C_whRVO8.js} +1 -1
  16. package/dist/assets/arrays-0R2reF2y.js +1 -0
  17. package/dist/assets/{blob-iOBwGaP9.js → blob-e6VlOmQP.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-CR-tZNnc.js → cell-editor-3cVIcyHK.js} +6 -6
  22. package/dist/assets/cell-link-DFqfW-Ym.js +1 -0
  23. package/dist/assets/{cells-rA_jsZCj.js → cells-T9_Y4Jlv.js} +56 -56
  24. package/dist/assets/{chat-display-luF7psRA.js → chat-display-BtaARI-N.js} +1 -1
  25. package/dist/assets/chat-panel-eT2lfQrB.js +4 -0
  26. package/dist/assets/chat-ui-nNFa2LdU.js +4 -0
  27. package/dist/assets/{circle-DXf-VWZG.js → circle-B6D_6D8w.js} +1 -1
  28. package/dist/assets/code-visibility-CV3S4xuf.js +1 -0
  29. package/dist/assets/coerce-DWjFeXOY.js +1 -0
  30. package/dist/assets/{column-preview-uosAAL4X.js → column-preview-C6uXRJry.js} +1 -1
  31. package/dist/assets/command-palette-OZPTmtR-.js +1 -0
  32. package/dist/assets/{common-lisZ0l6a.js → common-DeeLgjWQ.js} +1 -1
  33. package/dist/assets/{components-CP_UoFtT.js → components-DrU8dOzy.js} +1 -1
  34. package/dist/assets/{components-S7ZwOrvO.js → components-DyAF3_Nf.js} +1 -1
  35. package/dist/assets/{components-CXfO-F47.js → components-lDm0qi2t.js} +1 -1
  36. package/dist/assets/config-CjQ4WJnJ.js +1 -0
  37. package/dist/assets/config-DGX7JYor.js +1 -0
  38. package/dist/assets/config-DzdxZb17.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-V0-52XOh.js → datasource-gqFgG9Qd.js} +1 -1
  43. package/dist/assets/{dates-CfmdTL6B.js → dates-CQMwFko2.js} +1 -1
  44. package/dist/assets/{dependency-graph-panel-B0uP-_id.js → dependency-graph-panel-BFFxLgR6.js} +1 -1
  45. package/dist/assets/{dist-iaThVDkA.js → dist-jlhMTKo_.js} +1 -1
  46. package/dist/assets/{documentation-panel-DVl_ebWy.js → documentation-panel-BBpGbT-f.js} +1 -1
  47. package/dist/assets/{download-Bx55LMki.js → download-C8Gw-D0Y.js} +1 -1
  48. package/dist/assets/{dropdown-menu-C1rnl-0o.js → dropdown-menu-DS6umAnH.js} +1 -1
  49. package/dist/assets/{edit-page-CGyjT-CQ.js → edit-page-B0lN_Rib.js} +6 -6
  50. package/dist/assets/{error-banner-Cd69vvyX.js → error-banner-B5kzVf_0.js} +1 -1
  51. package/dist/assets/{error-entries-DOWpdGvI.js → error-entries-uyqOG5tg.js} +1 -1
  52. package/dist/assets/{error-panel-C-uxLOHF.js → error-panel-Cli5zPWi.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-B99ThNJW.js → feedback-button-CvrwBGr7.js} +2 -2
  56. package/dist/assets/feedback-button-DknbRCN_.js +1 -0
  57. package/dist/assets/file-explorer-panel-BSU5ultY.js +1 -0
  58. package/dist/assets/{file-name-input-ChnBsDE_.js → file-name-input-CDrRlIiX.js} +1 -1
  59. package/dist/assets/{floating-outline-MYqQW4Vp.js → floating-outline-GQD9km0W.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-DSL0J4md.js → gallery-page-CxmV1JLF.js} +1 -1
  64. package/dist/assets/{ganttDiagram-EL5Y4UJY-2n7Ig5Jm.js → ganttDiagram-EL5Y4UJY-CYqkCfFG.js} +1 -1
  65. package/dist/assets/{glide-data-editor-B1MWlKeM.js → glide-data-editor-B1kpoetH.js} +2 -2
  66. package/dist/assets/{glide-utils-CIrAxdjv.js → glide-utils-HFqhvWHI.js} +2 -2
  67. package/dist/assets/{home-page-B1M3X9Yf.js → home-page-KnJyha6O.js} +3 -3
  68. package/dist/assets/{hooks-Bj0C0zrZ.js → hooks-w6zKs3CJ.js} +1 -1
  69. package/dist/assets/{html-to-image-DK9Zl1lU.js → html-to-image-D_0rd3_B.js} +1 -1
  70. package/dist/assets/index-Bu8_lPMx.js +30 -0
  71. package/dist/assets/index-evFWHKmM.css +2 -0
  72. package/dist/assets/{input-BoBxR50a.js → input-BZdRe7pb.js} +1 -1
  73. package/dist/assets/{kiosk-mode-BZECfg9k.js → kiosk-mode-BGmMSc7A.js} +1 -1
  74. package/dist/assets/layout-sYdHI-lW.js +9 -0
  75. package/dist/assets/linear-Bma8J3iM.js +1 -0
  76. package/dist/assets/links-CXcpNIM0.js +1 -0
  77. package/dist/assets/{logs-panel-y0XnfulJ.js → logs-panel-CdUc5vtA.js} +1 -1
  78. package/dist/assets/{markdown-renderer-Q5AhbRU3.js → markdown-renderer-pKc_bFac.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-BNOnF6-0.js → name-cell-input-csH3Cy54.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-Be7stWwv.js → outline-panel-DhsHYTuo.js} +1 -1
  86. package/dist/assets/packages-panel-lkNX6Oov.js +1 -0
  87. package/dist/assets/{panel-accordion-state-GeMEdCPw.js → panel-accordion-state-Bco6nsUI.js} +1 -1
  88. package/dist/assets/panels-B2UqDbHy.js +107 -0
  89. package/dist/assets/precisionRound-BxlUs8G4.js +1 -0
  90. package/dist/assets/process-output-B2-bT4cq.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-YULOXf4h.js → readonly-code-display-Dsp6vWPh.js} +1 -1
  94. package/dist/assets/{readonly-python-code-CadgXgRu.js → readonly-python-code-B81AjzXM.js} +1 -1
  95. package/dist/assets/{renderShortcut-BkVJQ_Zh.js → renderShortcut-H60uiHBp.js} +1 -1
  96. package/dist/assets/{reveal-component-B9FLjTg_.js → reveal-component-CrQRQhW4.js} +37 -37
  97. package/dist/assets/run-page-BGzpa3D9.js +1 -0
  98. package/dist/assets/{runs-BLp5jezv.js → runs-6lJxKV1w.js} +1 -1
  99. package/dist/assets/{runtime-details-state-DuChzsA6.js → runtime-details-state-CznIe40L.js} +1 -1
  100. package/dist/assets/{schemas-Dv42wLFq.js → schemas-ChRqMOTL.js} +1 -1
  101. package/dist/assets/{scratchpad-panel-DtB0A8xj.js → scratchpad-panel-ClLxdqdK.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-Dh3v7XPE.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-B6tA28BD.js → session-panel-C7-wmZhc.js} +1 -1
  107. package/dist/assets/{snippets-panel-CQ1dn3lA.js → snippets-panel-BARpOPyS.js} +1 -1
  108. package/dist/assets/{spec-BfSBcO5B.js → spec-uCpVvaAR.js} +1 -1
  109. package/dist/assets/{state-BUsmcyaT.js → state-BRYi026f.js} +3 -3
  110. package/dist/assets/{state-uwRvJLb8.js → state-BY4_P6ko.js} +1 -1
  111. package/dist/assets/{state-Bfh_2SLl.js → state-Bye6rCo4.js} +1 -1
  112. package/dist/assets/{state-BAJYRhmO.js → state-C0irhlOZ.js} +1 -1
  113. package/dist/assets/{state-DxQnC_1I.js → state-Ck7FMD5A.js} +1 -1
  114. package/dist/assets/{state-C3oDoyFe.js → state-DaWgt-rn.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-IoWE8PbW.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-Bhcn_y1N.js} +1 -1
  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-BW2ZVnXs.js +1 -0
  125. package/dist/assets/{tracing-panel-dgnr2fIN.js → tracing-panel-UKdh_YXC.js} +2 -2
  126. package/dist/assets/{tree-actions-DAd80MbP.js → tree-actions-Cb-nUK1F.js} +1 -1
  127. package/dist/assets/{use-restore-focus-BxEGwIbk.js → use-restore-focus-DdS7Tl0F.js} +1 -1
  128. package/dist/assets/{useAddCell-DGstJK0S.js → useAddCell-Bielffe0.js} +1 -1
  129. package/dist/assets/useAsyncData-DhhdOGGd.js +1 -0
  130. package/dist/assets/useBoolean-CMgj9hnq.js +1 -0
  131. package/dist/assets/{useCellActionButton-DY9RlJTb.js → useCellActionButton-Cu34Zs6x.js} +1 -1
  132. package/dist/assets/useDebounce-k9QTpQ9-.js +1 -0
  133. package/dist/assets/useDelayElapsed-BlsDT4TV.js +1 -0
  134. package/dist/assets/{useDeleteCell-DFBGlSQk.js → useDeleteCell-BV4VemCv.js} +1 -1
  135. package/dist/assets/{useDependencyPanelTab-DydxsE5d.js → useDependencyPanelTab-BEGqRiWi.js} +1 -1
  136. package/dist/assets/{useEventListener-D9cftcHj.js → useEventListener-CjDeLqFC.js} +1 -1
  137. package/dist/assets/{useIframeCapabilities-CwTkczVN.js → useIframeCapabilities-Czr9e4uG.js} +1 -1
  138. package/dist/assets/{useInstallPackage-iX1eufdl.js → useInstallPackage-C7neMPwh.js} +1 -1
  139. package/dist/assets/{useInterval--x3s1VpI.js → useInterval-DJdQmXnq.js} +1 -1
  140. package/dist/assets/{useNotebookActions-CupIPSlg.js → useNotebookActions-OLol05t9.js} +1 -1
  141. package/dist/assets/{useRunCells-BFjFX3hz.js → useRunCells-BFeZMi7f.js} +1 -1
  142. package/dist/assets/{useSplitCell-Tqv5pPtL.js → useSplitCell-DllkNFke.js} +1 -1
  143. package/dist/assets/useStateForKey-CGMJQT8x.js +1 -0
  144. package/dist/assets/{useTheme-COhSHkCj.js → useTheme-DLtPc-39.js} +1 -1
  145. package/dist/assets/{utils-CaQYvazl.js → utils-DfElfBpz.js} +1 -1
  146. package/dist/assets/vega-component-D7JFthDD.js +1 -0
  147. package/dist/assets/{vega-embed-container-DJPNkHPb.js → vega-embed-container-DtArSJz1.js} +1 -1
  148. package/dist/assets/vega-loader.browser-Bu25TKax.js +6 -0
  149. package/dist/assets/{write-secret-modal-DzKAInL2.js → write-secret-modal-7DzehGyr.js} +1 -1
  150. package/dist/assets/{xychartDiagram-S5SC5T6Z-KbGzG6-u.js → xychartDiagram-S5SC5T6Z-CeRnBS5e.js} +1 -1
  151. package/dist/index.html +76 -74
  152. package/package.json +4 -4
  153. package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
  154. package/src/__tests__/host-imports.test.ts +1 -0
  155. package/src/__tests__/mount.test.ts +8 -8
  156. package/src/components/app-config/ai-config.tsx +6 -2
  157. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  158. package/src/components/chat/chat-panel.tsx +3 -7
  159. package/src/components/chat/chat-utils.ts +4 -8
  160. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  161. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  162. package/src/components/data-table/__tests__/export-actions.test.tsx +434 -6
  163. package/src/components/data-table/charts/charts.tsx +14 -19
  164. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  165. package/src/components/data-table/columns.tsx +2 -1
  166. package/src/components/data-table/context-menu.tsx +36 -20
  167. package/src/components/data-table/date-popover.tsx +6 -4
  168. package/src/components/data-table/export-actions.tsx +267 -163
  169. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +2 -2
  170. package/src/components/data-table/schemas.ts +14 -36
  171. package/src/components/editor/Output.tsx +5 -1
  172. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  173. package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +80 -252
  174. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +210 -34
  175. package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
  176. package/src/components/editor/actions/pair-with-agent-modal.tsx +100 -37
  177. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  178. package/src/components/editor/cell/CreateCellButton.tsx +8 -10
  179. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  180. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  181. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  182. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  183. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  184. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
  185. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  186. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +277 -78
  187. package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +196 -41
  188. package/src/components/editor/connections/components.tsx +1 -1
  189. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  190. package/src/components/editor/controls/command-palette.tsx +22 -10
  191. package/src/components/editor/dynamic-favicon.tsx +21 -40
  192. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +139 -36
  193. package/src/components/editor/errors/send-error-report-button.tsx +10 -7
  194. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  195. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  196. package/src/components/editor/file-tree/file-explorer.tsx +10 -5
  197. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  198. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  199. package/src/components/editor/output/MarimoTracebackOutput.tsx +28 -22
  200. package/src/components/editor/output/console/ConsoleOutput.tsx +8 -18
  201. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  202. package/src/components/icons/large-spinner.tsx +3 -14
  203. package/src/components/pages/home-page.tsx +4 -4
  204. package/src/components/slides/slide-cell-view.tsx +1 -1
  205. package/src/components/tracing/tracing.tsx +2 -2
  206. package/src/core/attachments/__tests__/toast.test.tsx +58 -0
  207. package/src/core/attachments/state.ts +5 -8
  208. package/src/core/attachments/toast.ts +15 -0
  209. package/src/core/cells/__tests__/error-report.test.ts +53 -3
  210. package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
  211. package/src/core/cells/__tests__/session.test.ts +18 -3
  212. package/src/core/cells/cells.ts +2 -1
  213. package/src/core/cells/error-report.ts +14 -6
  214. package/src/core/cells/pending-delete-service.ts +2 -6
  215. package/src/core/cells/session.ts +19 -1
  216. package/src/core/config/pair.ts +5 -3
  217. package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
  218. package/src/core/datasets/data-source-discovery.ts +4 -6
  219. package/src/core/edit-app.tsx +2 -2
  220. package/src/core/host/AGENTS.md +11 -2
  221. package/src/core/host/__tests__/attachments.test.ts +56 -0
  222. package/src/core/host/__tests__/document.test.ts +126 -0
  223. package/src/core/host/__tests__/events.test.ts +49 -0
  224. package/src/core/host/__tests__/fixtures.ts +41 -0
  225. package/src/core/host/__tests__/notebook-client.test.ts +496 -0
  226. package/src/core/host/connection.ts +81 -0
  227. package/src/core/host/events.ts +68 -0
  228. package/src/core/host/notebook-client.ts +316 -0
  229. package/src/core/host/resources/attachments.ts +19 -0
  230. package/src/core/host/resources/document.ts +101 -0
  231. package/src/core/hotkeys/actions.ts +2 -4
  232. package/src/core/islands/__tests__/bootstrap.test.ts +0 -26
  233. package/src/core/islands/bootstrap.ts +0 -1
  234. package/src/core/network/connection.ts +4 -2
  235. package/src/core/network/useConnectionNotice.ts +8 -2
  236. package/src/core/run-app.tsx +2 -2
  237. package/src/core/runtime/__tests__/runtime.test.ts +5 -12
  238. package/src/core/runtime/runtime.ts +5 -5
  239. package/src/core/static/files.ts +2 -0
  240. package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -17
  241. package/src/core/websocket/__tests__/{useWebSocket.test.ts → createConnectionTransport.test.ts} +1 -1
  242. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +149 -0
  243. package/src/core/websocket/__tests__/{useMarimoKernelConnection.hook.test.tsx → useNotebookSession.hook.test.tsx} +168 -253
  244. package/src/core/websocket/connection-state.ts +70 -0
  245. package/src/core/websocket/state.ts +7 -0
  246. package/src/core/websocket/transports/__tests__/sse.test.ts +18 -4
  247. package/src/core/websocket/transports/create.ts +31 -0
  248. package/src/core/websocket/transports/sse.ts +14 -2
  249. package/src/core/websocket/transports/transport.ts +12 -23
  250. package/src/core/websocket/types.ts +9 -39
  251. package/src/core/websocket/useNotebookClient.ts +78 -0
  252. package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +53 -233
  253. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  254. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  255. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  256. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  257. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  258. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  259. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  260. package/src/hooks/useAsyncData.ts +3 -3
  261. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  262. package/src/hooks/useDebounce.ts +5 -8
  263. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  264. package/src/hooks/useDelayElapsed.ts +14 -10
  265. package/src/hooks/useElapsedTime.ts +3 -3
  266. package/src/hooks/useInputHistory.ts +4 -7
  267. package/src/hooks/useInternalStateWithSync.ts +6 -6
  268. package/src/hooks/useResizeObserver.ts +4 -7
  269. package/src/hooks/useScript.ts +67 -70
  270. package/src/hooks/useStateForKey.ts +93 -0
  271. package/src/mount.tsx +3 -3
  272. package/src/plugins/core/registerReactComponent.tsx +1 -1
  273. package/src/plugins/impl/DataTablePlugin.tsx +14 -15
  274. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  275. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  276. package/src/plugins/impl/FormPlugin.tsx +1 -1
  277. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  278. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +201 -1
  279. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  280. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  281. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
  282. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  283. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  284. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  285. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  286. package/src/plugins/impl/chat/chat-ui.tsx +10 -9
  287. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  288. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  289. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  290. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  291. package/src/plugins/impl/panel/PanelPlugin.tsx +15 -16
  292. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  293. package/src/plugins/impl/panel/utils.ts +17 -0
  294. package/src/plugins/impl/vega/make-selectable.ts +3 -6
  295. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  296. package/src/stories/editor.stories.tsx +24 -17
  297. package/src/utils/__tests__/arrays.test.ts +35 -0
  298. package/src/utils/__tests__/numbers.test.ts +48 -1
  299. package/src/utils/__tests__/objects.test.ts +21 -0
  300. package/src/utils/arrays.ts +10 -0
  301. package/src/utils/numbers.ts +28 -17
  302. package/src/utils/objects.ts +3 -7
  303. package/src/utils/once.ts +19 -19
  304. package/dist/assets/CellStatus-nZpuDkm7.js +0 -1
  305. package/dist/assets/JsonOutput-d-k8v_17.js +0 -11
  306. package/dist/assets/MarimoErrorOutput-CLYUxiKW.js +0 -10
  307. package/dist/assets/RunButton-CFQUNN5c.js +0 -1
  308. package/dist/assets/ai-model-dropdown-C6GHHEdR.js +0 -5
  309. package/dist/assets/any-language-editor-BLCAD3yx.js +0 -1
  310. package/dist/assets/arrays-BqUx5USx.js +0 -1
  311. package/dist/assets/cell-link-1CynTVGQ.js +0 -1
  312. package/dist/assets/chat-panel-DDDTnLvB.js +0 -4
  313. package/dist/assets/chat-ui-nV_m4Btr.js +0 -4
  314. package/dist/assets/code-visibility-B-_GvwR5.js +0 -1
  315. package/dist/assets/coerce-CpE6kUdX.js +0 -1
  316. package/dist/assets/command-palette-BZ13RKDJ.js +0 -1
  317. package/dist/assets/config-DX8Y2WbJ.js +0 -1
  318. package/dist/assets/config-FzDtjF1-.js +0 -1
  319. package/dist/assets/config-VPSq73n8.js +0 -1
  320. package/dist/assets/feedback-button-DlqZkJwz.js +0 -1
  321. package/dist/assets/file-explorer-panel-D3CphidS.js +0 -1
  322. package/dist/assets/index-CXIE-QEG.css +0 -2
  323. package/dist/assets/index-DnIgeCyQ.js +0 -29
  324. package/dist/assets/layout-DBuMeWh0.js +0 -9
  325. package/dist/assets/linear-0eij_aAG.js +0 -1
  326. package/dist/assets/links-DkkL9DVa.js +0 -1
  327. package/dist/assets/numbers-Mk3-CcoU.js +0 -1
  328. package/dist/assets/packages-panel-9Qs1npCZ.js +0 -1
  329. package/dist/assets/panels-B9x935By.js +0 -4
  330. package/dist/assets/precisionRound-Bq_nmf5u.js +0 -1
  331. package/dist/assets/process-output-DNFKMODr.js +0 -1
  332. package/dist/assets/react-vega-C3rYGtP6.js +0 -189
  333. package/dist/assets/run-page-CjdGaic3.js +0 -1
  334. package/dist/assets/tooltip-mT3CGest.js +0 -61
  335. package/dist/assets/tracing-BARlJ_Dk.js +0 -1
  336. package/dist/assets/useAsyncData-C7aJJz0Y.js +0 -1
  337. package/dist/assets/useBoolean-BDD6_xQQ.js +0 -1
  338. package/dist/assets/useDebounce-B_ccBZTp.js +0 -1
  339. package/dist/assets/vega-component-CnadTgoE.js +0 -1
  340. package/dist/assets/vega-loader.browser-CLRkNgm7.js +0 -6
  341. package/src/core/host/__tests__/protocol.test.ts +0 -214
  342. package/src/core/host/protocol.ts +0 -1943
  343. package/src/core/websocket/useWebSocket.tsx +0 -88
@@ -1,7 +1,7 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import { CheckIcon, CopyIcon } from "lucide-react";
4
- import React, { useEffect, useState } from "react";
4
+ import React, { useEffect, useMemo, useState } from "react";
5
5
  import { useAtomValue } from "jotai";
6
6
  import { Button } from "@/components/ui/button";
7
7
  import {
@@ -17,18 +17,16 @@ import { Events } from "@/utils/events";
17
17
  import { Tooltip } from "@/components/ui/tooltip";
18
18
  import { asRemoteURL, useRuntimeManager } from "@/core/runtime/config";
19
19
  import { API } from "@/core/network/api";
20
- import { pairPreviewAtom } from "@/core/config/pair";
20
+ import { pairConfigAtom } from "@/core/config/pair";
21
21
  import { stableSessionIdAtom } from "@/core/kernel/session";
22
22
  import {
23
23
  AGENT_LABELS,
24
24
  AGENT_TABS,
25
25
  type AgentTab,
26
26
  type ConnectionInfo,
27
- getFileFromURL,
28
27
  getRawPrompt,
29
28
  getTerminalCommand,
30
29
  maskToken,
31
- SKILL_INSTALL,
32
30
  TERMINAL_TABS,
33
31
  } from "./pair-with-agent-commands";
34
32
 
@@ -52,15 +50,67 @@ export const PairWithAgentModal: React.FC<{
52
50
  }> = ({ onClose }) => {
53
51
  const [activeTab, setActiveTab] = useState<AgentTab>("claude");
54
52
  const runtimeManager = useRuntimeManager();
55
- const preview = useAtomValue(pairPreviewAtom);
53
+ const config = useAtomValue(pairConfigAtom);
54
+ const hostEnabled = config?.hostEnabled ?? false;
56
55
  const stableSessionId = useAtomValue(stableSessionIdAtom);
57
- const commandStep = preview ? 1 : 2;
56
+ const lookupIdentity = useMemo(
57
+ () => ({ hostEnabled, stableSessionId }),
58
+ [hostEnabled, stableSessionId],
59
+ );
60
+ const [lookup, setLookup] = useState<
61
+ {
62
+ identity: typeof lookupIdentity;
63
+ } & ({ status: "ready"; notebookId: string | null } | { status: "error" })
64
+ >();
65
+ useEffect(() => {
66
+ const { hostEnabled, stableSessionId: sessionId } = lookupIdentity;
67
+ if (!hostEnabled || !sessionId) {
68
+ return;
69
+ }
70
+ let cancelled = false;
71
+ API.get<Record<string, { session_id: string; notebook_id: string | null }>>(
72
+ "/sessions",
73
+ )
74
+ .then((sessions) => {
75
+ if (!cancelled) {
76
+ const session = Object.values(sessions).find(
77
+ (session) => session.session_id === sessionId,
78
+ );
79
+ setLookup(
80
+ session
81
+ ? {
82
+ identity: lookupIdentity,
83
+ status: "ready",
84
+ notebookId: session.notebook_id,
85
+ }
86
+ : { identity: lookupIdentity, status: "error" },
87
+ );
88
+ }
89
+ })
90
+ .catch(() => {
91
+ if (!cancelled) {
92
+ setLookup({ identity: lookupIdentity, status: "error" });
93
+ }
94
+ });
95
+ return () => {
96
+ cancelled = true;
97
+ };
98
+ }, [lookupIdentity]);
99
+ const currentLookup =
100
+ lookup?.identity === lookupIdentity ? lookup : undefined;
101
+ const verified = !hostEnabled || currentLookup?.status === "ready";
102
+ const notebookId =
103
+ hostEnabled && currentLookup?.status === "ready"
104
+ ? currentLookup.notebookId
105
+ : undefined;
106
+ const targetReady = Boolean(stableSessionId) && verified;
58
107
  const authToken = useAuthToken();
59
108
  const hasToken = Boolean(authToken);
60
109
  const connection: ConnectionInfo = {
61
110
  url: runtimeManager.httpURL.toString(),
62
- file: preview ? undefined : getFileFromURL(window.location.href),
63
- session: preview ? (stableSessionId ?? undefined) : undefined,
111
+ notebook: notebookId ?? undefined,
112
+ session:
113
+ targetReady && !notebookId ? (stableSessionId ?? undefined) : undefined,
64
114
  };
65
115
 
66
116
  return (
@@ -83,6 +133,13 @@ export const PairWithAgentModal: React.FC<{
83
133
  </DialogHeader>
84
134
 
85
135
  <div className="flex flex-col gap-4 py-2">
136
+ {!targetReady && (
137
+ <output className="text-sm text-muted-foreground">
138
+ {currentLookup?.status === "error"
139
+ ? "Could not verify this notebook. Close this dialog and try again."
140
+ : "Waiting for the notebook connection…"}
141
+ </output>
142
+ )}
86
143
  <Tabs
87
144
  value={activeTab}
88
145
  onValueChange={(v) => setActiveTab(v as AgentTab)}
@@ -101,30 +158,27 @@ export const PairWithAgentModal: React.FC<{
101
158
  value={tab}
102
159
  className="mt-4 flex flex-col gap-4"
103
160
  >
104
- {!preview && (
105
- <Step
106
- index={1}
107
- title="Install the skill"
108
- hint="Run once per machine."
109
- >
110
- <CommandBlock command={SKILL_INSTALL} />
111
- </Step>
112
- )}
113
- <Step index={commandStep} title="Run in your terminal">
161
+ <Step
162
+ index={1}
163
+ title="Run in your terminal"
164
+ hint={
165
+ hostEnabled && tab === "opencode"
166
+ ? "Agent connections require OpenCode 2.x."
167
+ : undefined
168
+ }
169
+ >
114
170
  <CommandBlock
115
171
  command={getTerminalCommand(
116
172
  tab,
117
173
  connection,
118
174
  hasToken,
119
- preview,
175
+ config,
120
176
  )}
177
+ disabled={!targetReady}
121
178
  />
122
179
  </Step>
123
180
  {hasToken && authToken && (
124
- <Step
125
- index={commandStep + 1}
126
- title="Paste when prompted for a token"
127
- >
181
+ <Step index={2} title="Paste when prompted for a token">
128
182
  <CommandBlock
129
183
  command={authToken}
130
184
  display={maskToken(authToken)}
@@ -136,17 +190,8 @@ export const PairWithAgentModal: React.FC<{
136
190
  ))}
137
191
 
138
192
  <TabsContent value="prompt" className="mt-4 flex flex-col gap-4">
139
- {!preview && (
140
- <Step
141
- index={1}
142
- title="Make sure the marimo-pair skill is available to your agent"
143
- hint="Skip if your agent already has it."
144
- >
145
- <CommandBlock command={SKILL_INSTALL} />
146
- </Step>
147
- )}
148
193
  <Step
149
- index={commandStep}
194
+ index={1}
150
195
  title="Copy this prompt into your agent"
151
196
  hint={
152
197
  hasToken
@@ -155,13 +200,14 @@ export const PairWithAgentModal: React.FC<{
155
200
  }
156
201
  >
157
202
  <CommandBlock
158
- command={getRawPrompt(connection, authToken, preview)}
203
+ command={getRawPrompt(connection, authToken, config)}
159
204
  display={getRawPrompt(
160
205
  connection,
161
206
  authToken ? maskToken(authToken) : null,
162
- preview,
207
+ config,
163
208
  )}
164
209
  multiline={true}
210
+ disabled={!targetReady}
165
211
  />
166
212
  </Step>
167
213
  </TabsContent>
@@ -199,11 +245,21 @@ const CommandBlock: React.FC<{
199
245
  display?: string;
200
246
  multiline?: boolean;
201
247
  copyLabel?: string;
202
- }> = ({ command, display, multiline = false, copyLabel = "Copy command" }) => {
248
+ disabled?: boolean;
249
+ }> = ({
250
+ command,
251
+ display,
252
+ multiline = false,
253
+ copyLabel = "Copy command",
254
+ disabled = false,
255
+ }) => {
203
256
  const [copied, setCopied] = useState(false);
204
257
 
205
258
  const copy = Events.stopPropagation(async (e) => {
206
259
  e.preventDefault();
260
+ if (disabled) {
261
+ return;
262
+ }
207
263
  await copyToClipboard(command);
208
264
  setCopied(true);
209
265
  setTimeout(() => setCopied(false), 2000);
@@ -218,6 +274,7 @@ const CommandBlock: React.FC<{
218
274
  <Tooltip content="Copied!" open={copied}>
219
275
  <Button
220
276
  aria-label="Copy prompt"
277
+ disabled={disabled}
221
278
  onClick={copy}
222
279
  size="xs"
223
280
  variant="ghost"
@@ -240,7 +297,13 @@ const CommandBlock: React.FC<{
240
297
  {display ?? command}
241
298
  </code>
242
299
  <Tooltip content="Copied!" open={copied}>
243
- <Button aria-label={copyLabel} onClick={copy} size="xs" variant="ghost">
300
+ <Button
301
+ aria-label={copyLabel}
302
+ onClick={copy}
303
+ size="xs"
304
+ variant="ghost"
305
+ disabled={disabled}
306
+ >
244
307
  {copied ? (
245
308
  <CheckIcon size={14} strokeWidth={1.5} />
246
309
  ) : (
@@ -106,9 +106,9 @@ export const AddCellWithAI: React.FC<{
106
106
  const runtimeManager = useRuntimeManager();
107
107
 
108
108
  const inputRef = useRef<ReactCodeMirrorRef>(null);
109
- const submissionController = useRef(
110
- new StagedCellSubmissionController(),
111
- ).current;
109
+ const [submissionController] = useState(
110
+ () => new StagedCellSubmissionController(),
111
+ );
112
112
 
113
113
  const fileInputRef = useRef<HTMLInputElement>(null);
114
114
  const { files, addFiles, removeFile } = useFileState();
@@ -1,6 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  import { DatabaseIcon, DiamondPlusIcon, PlusIcon } from "lucide-react";
3
- import { useState } from "react";
3
+ import { useRef, useState } from "react";
4
4
  import { Button } from "@/components/editor/inputs/Inputs";
5
5
  import { MinimalHotkeys } from "@/components/shortcuts/renderShortcut";
6
6
  import {
@@ -36,7 +36,8 @@ export const CreateCellButton = ({
36
36
  const { createNewCell, addSetupCellIfDoesntExist } = useCellActions();
37
37
  const shortcut = `${oneClickShortcut}-Click`;
38
38
  const [open, setOpen] = useState(false);
39
- const [justOpened, setJustOpened] = useState(false);
39
+ // Time the dropdown last opened, to ignore the click that opened it
40
+ const openedAt = useRef(0);
40
41
 
41
42
  const baseTooltipContent =
42
43
  getConnectionTooltip(connectionState) || tooltipContent;
@@ -83,9 +84,7 @@ export const CreateCellButton = ({
83
84
 
84
85
  const openDropdown = () => {
85
86
  setOpen(true);
86
- setJustOpened(true);
87
- // Allow interactions after a brief delay to prevent the dropdown items immediately being clicked
88
- setTimeout(() => setJustOpened(false), 200);
87
+ openedAt.current = Date.now();
89
88
  };
90
89
 
91
90
  // We use onPointerDownCapture (not onPointerDown) to intercept events in
@@ -121,15 +120,14 @@ export const CreateCellButton = ({
121
120
  const handleOpenChange = (isOpen: boolean) => {
122
121
  setOpen(isOpen);
123
122
  if (isOpen) {
124
- setJustOpened(true);
125
- // Allow interactions after a brief delay
126
- setTimeout(() => setJustOpened(false), 200);
123
+ openedAt.current = Date.now();
127
124
  }
128
125
  };
129
126
 
130
127
  const handleFirstItemClick = (e: React.MouseEvent) => {
131
- // Hack to prevent the first item from being clicked when the dropdown is opened
132
- if (justOpened) {
128
+ // Hack to prevent the first item from being clicked when the dropdown is
129
+ // opened: allow interactions only after a brief delay
130
+ if (Date.now() - openedAt.current < 200) {
133
131
  e.preventDefault();
134
132
  e.stopPropagation();
135
133
  return;
@@ -0,0 +1,39 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { act, renderHook } from "@testing-library/react";
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { useShouldShowInterrupt } from "../useShouldShowInterrupt";
6
+
7
+ describe("useShouldShowInterrupt", () => {
8
+ beforeEach(() => vi.useFakeTimers());
9
+ afterEach(() => vi.useRealTimers());
10
+
11
+ it("waits for each run and hides immediately when it stops", () => {
12
+ const { result, rerender } = renderHook(useShouldShowInterrupt, {
13
+ initialProps: false,
14
+ });
15
+ expect(result.current).toBe(false);
16
+ rerender(true);
17
+ expect(result.current).toBe(false);
18
+ act(() => vi.advanceTimersByTime(200));
19
+ expect(result.current).toBe(true);
20
+ rerender(false);
21
+ expect(result.current).toBe(false);
22
+ rerender(true);
23
+ expect(result.current).toBe(false);
24
+ act(() => vi.advanceTimersByTime(199));
25
+ expect(result.current).toBe(false);
26
+ act(() => vi.advanceTimersByTime(1));
27
+ expect(result.current).toBe(true);
28
+ });
29
+
30
+ it("cancels the delay for a short run", () => {
31
+ const { result, rerender } = renderHook(useShouldShowInterrupt, {
32
+ initialProps: true,
33
+ });
34
+ act(() => vi.advanceTimersByTime(100));
35
+ rerender(false);
36
+ act(() => vi.advanceTimersByTime(200));
37
+ expect(result.current).toBe(false);
38
+ });
39
+ });
@@ -0,0 +1,54 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { EditorState } from "@codemirror/state";
4
+ import { EditorView } from "@codemirror/view";
5
+ import { fireEvent, render, screen } from "@testing-library/react";
6
+ import { createRef } from "react";
7
+ import { afterEach, describe, expect, it, vi } from "vitest";
8
+ import { TooltipProvider } from "@/components/ui/tooltip";
9
+ import { switchLanguage } from "@/core/codemirror/language/extension";
10
+ import { LanguageToggle } from "../language-toggle";
11
+
12
+ vi.mock("@/core/codemirror/language/extension", () => ({
13
+ switchLanguage: vi.fn(),
14
+ }));
15
+
16
+ describe("LanguageToggle", () => {
17
+ afterEach(() => {
18
+ vi.mocked(switchLanguage).mockClear();
19
+ });
20
+
21
+ it("uses the editor mounted after the button rendered", () => {
22
+ const editorViewRef = createRef<EditorView>();
23
+ const onAfterToggle = vi.fn();
24
+ render(
25
+ <TooltipProvider>
26
+ <LanguageToggle
27
+ editorViewRef={editorViewRef}
28
+ currentLanguageAdapter="python"
29
+ canSwitchToLanguage={true}
30
+ toType="markdown"
31
+ displayName="Markdown"
32
+ icon="Markdown"
33
+ onAfterToggle={onAfterToggle}
34
+ />
35
+ </TooltipProvider>,
36
+ );
37
+
38
+ fireEvent.click(screen.getByRole("button", { name: "Markdown" }));
39
+ expect(switchLanguage).not.toHaveBeenCalled();
40
+ expect(onAfterToggle).not.toHaveBeenCalled();
41
+
42
+ const view = new EditorView({ state: EditorState.create() });
43
+ editorViewRef.current = view;
44
+ try {
45
+ fireEvent.click(screen.getByRole("button", { name: "Markdown" }));
46
+ expect(switchLanguage).toHaveBeenCalledWith(view, {
47
+ language: "markdown",
48
+ });
49
+ expect(onAfterToggle).toHaveBeenCalledOnce();
50
+ } finally {
51
+ view.destroy();
52
+ }
53
+ });
54
+ });
@@ -572,7 +572,7 @@ const CellEditorInternal = ({
572
572
  <div className="absolute top-1 right-5">
573
573
  <LanguageToggles
574
574
  code={code}
575
- editorView={editorViewRef.current}
575
+ editorViewRef={editorViewRef}
576
576
  currentLanguageAdapter={languageAdapter}
577
577
  onAfterToggle={afterToggleMarkdown}
578
578
  />
@@ -13,7 +13,7 @@ import { Functions } from "@/utils/functions";
13
13
  import { MarkdownIcon, PythonIcon } from "./icons";
14
14
 
15
15
  interface LanguageTogglesProps {
16
- editorView: EditorView | null;
16
+ editorViewRef: React.RefObject<EditorView | null>;
17
17
  code: string;
18
18
  currentLanguageAdapter: LanguageAdapter["type"] | undefined;
19
19
  onAfterToggle: () => void;
@@ -25,7 +25,7 @@ interface LanguageTogglesProps {
25
25
  }
26
26
 
27
27
  export const LanguageToggles: React.FC<LanguageTogglesProps> = ({
28
- editorView,
28
+ editorViewRef,
29
29
  code,
30
30
  currentLanguageAdapter,
31
31
  onAfterToggle,
@@ -43,7 +43,7 @@ export const LanguageToggles: React.FC<LanguageTogglesProps> = ({
43
43
  return (
44
44
  <div className={className}>
45
45
  <LanguageToggle
46
- editorView={editorView}
46
+ editorViewRef={editorViewRef}
47
47
  currentLanguageAdapter={currentLanguageAdapter}
48
48
  canSwitchToLanguage={canUseSQL && currentLanguageAdapter === "python"}
49
49
  icon={
@@ -58,7 +58,7 @@ export const LanguageToggles: React.FC<LanguageTogglesProps> = ({
58
58
  onAfterToggle={onAfterToggle}
59
59
  />
60
60
  <LanguageToggle
61
- editorView={editorView}
61
+ editorViewRef={editorViewRef}
62
62
  currentLanguageAdapter={currentLanguageAdapter}
63
63
  canSwitchToLanguage={
64
64
  canUseMarkdown && currentLanguageAdapter === "python"
@@ -75,7 +75,7 @@ export const LanguageToggles: React.FC<LanguageTogglesProps> = ({
75
75
  onAfterToggle={onAfterToggle}
76
76
  />
77
77
  <LanguageToggle
78
- editorView={editorView}
78
+ editorViewRef={editorViewRef}
79
79
  currentLanguageAdapter={currentLanguageAdapter}
80
80
  canSwitchToLanguage={true}
81
81
  icon={
@@ -95,7 +95,7 @@ export const LanguageToggles: React.FC<LanguageTogglesProps> = ({
95
95
 
96
96
  interface Props {
97
97
  className?: string;
98
- editorView: EditorView | null;
98
+ editorViewRef: React.RefObject<EditorView | null>;
99
99
  canSwitchToLanguage: boolean;
100
100
  currentLanguageAdapter: LanguageAdapter["type"] | undefined;
101
101
  toType: LanguageAdapter["type"];
@@ -105,7 +105,7 @@ interface Props {
105
105
  }
106
106
 
107
107
  export const LanguageToggle: React.FC<Props> = ({
108
- editorView,
108
+ editorViewRef,
109
109
  currentLanguageAdapter,
110
110
  canSwitchToLanguage,
111
111
  icon,
@@ -114,6 +114,7 @@ export const LanguageToggle: React.FC<Props> = ({
114
114
  onAfterToggle,
115
115
  }) => {
116
116
  const handleClick = () => {
117
+ const editorView = editorViewRef.current;
117
118
  if (!editorView) {
118
119
  return;
119
120
  }
@@ -1,5 +1,5 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- import { useEffect, useState } from "react";
2
+ import { useDelayElapsed } from "@/hooks/useDelayElapsed";
3
3
 
4
4
  const DELAY_MS = 200;
5
5
 
@@ -10,17 +10,7 @@ const DELAY_MS = 200;
10
10
  export function useShouldShowInterrupt(running: boolean) {
11
11
  // Start a timer when the run starts.
12
12
  // After 200ms, show the interrupt button to avoid flickering.
13
- const [hasRunLongEnough, setHasRunLongEnough] = useState(false);
14
- useEffect(() => {
15
- if (!running) {
16
- return;
17
- }
18
- setHasRunLongEnough(false);
19
- const timeout = setTimeout(() => {
20
- setHasRunLongEnough(true);
21
- }, DELAY_MS);
22
- return () => clearTimeout(timeout);
23
- }, [running]);
13
+ const hasRunLongEnough = useDelayElapsed(running ? DELAY_MS : 0);
24
14
 
25
15
  return running && hasRunLongEnough;
26
16
  }
@@ -18,7 +18,10 @@ import type {
18
18
  DependencyTreeNode,
19
19
  DependencyTreeResponse,
20
20
  } from "@/core/network/types";
21
- import { withPackageInvalidation } from "@/core/packages/package-data";
21
+ import {
22
+ invalidatePackageData,
23
+ withPackageInvalidation,
24
+ } from "@/core/packages/package-data";
22
25
  import { useInstallPackages } from "@/core/packages/useInstallPackage";
23
26
  import { sandboxAtom, sandboxSyncAtom } from "@/core/packages/sandbox-state";
24
27
  import { store } from "@/core/state/jotai";
@@ -143,6 +146,32 @@ describe("PackagesPanel", () => {
143
146
  expect(screen.queryByText("cycle")).not.toBeInTheDocument();
144
147
  });
145
148
 
149
+ it("collapses expanded nodes when the dependency tree is refreshed", async () => {
150
+ const { client } = renderPanel(
151
+ { kind: "sandbox", backend: "pixi" },
152
+ populatedTree,
153
+ );
154
+ fireEvent.click(await screen.findByRole("treeitem", { name: /polars/ }));
155
+ expect(screen.getByText("already in tree")).toBeInTheDocument();
156
+
157
+ vi.mocked(client.getDependencyTree).mockResolvedValue({
158
+ tree: structuredClone(populatedTree),
159
+ context: { kind: "sandbox", backend: "pixi" },
160
+ });
161
+ act(() => invalidatePackageData());
162
+
163
+ await waitFor(() =>
164
+ expect(client.getDependencyTree).toHaveBeenCalledTimes(2),
165
+ );
166
+ await waitFor(() =>
167
+ expect(screen.queryByText("already in tree")).not.toBeInTheDocument(),
168
+ );
169
+ expect(screen.getByRole("treeitem", { name: /polars/ })).toHaveAttribute(
170
+ "aria-expanded",
171
+ "false",
172
+ );
173
+ });
174
+
146
175
  it("keeps the configured package manager changeable outside a sandbox", async () => {
147
176
  const { getPackageList } = renderPanel({
148
177
  kind: "package-manager",
@@ -31,6 +31,7 @@ import { usePackageAction } from "@/core/packages/usePackageAction";
31
31
  import { usePackageDependencies } from "@/core/packages/usePackageDependencies";
32
32
  import { useInstallPackages } from "@/core/packages/useInstallPackage";
33
33
  import { isWasm } from "@/core/wasm/utils";
34
+ import { useStateForKey } from "@/hooks/useStateForKey";
34
35
  import { ErrorBanner } from "@/plugins/impl/common/error-banner";
35
36
  import { cn } from "@/utils/cn";
36
37
  import { copyToClipboard } from "@/utils/copy";
@@ -440,15 +441,11 @@ const DependencyTree: React.FC<{
440
441
  error?: Error | null;
441
442
  sandboxBackend: "uv" | "pixi" | null;
442
443
  }> = ({ tree, error, sandboxBackend }) => {
443
- const [expandedNodes, setExpandedNodes] = React.useState<Set<string>>(
444
- new Set(),
444
+ const [expandedNodes, setExpandedNodes] = useStateForKey(
445
+ tree,
446
+ () => new Set<string>(),
445
447
  );
446
448
 
447
- // Reset tree to collapsed state when tree data changes (including refetches)
448
- React.useEffect(() => {
449
- setExpandedNodes(new Set());
450
- }, [tree]);
451
-
452
449
  if (error) {
453
450
  return <ErrorBanner error={error} />;
454
451
  }