@marimo-team/frontend 0.25.3-dev13 → 0.25.3-dev134

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 (565) hide show
  1. package/dist/assets/CellStatus-DC6FFQOg.js +1 -0
  2. package/dist/assets/{ConnectedDataExplorerComponent-TprnxZeJ.js → ConnectedDataExplorerComponent-BV1mVpaE.js} +1 -1
  3. package/dist/assets/{ErrorBoundary-CM13wrd8.js → ErrorBoundary-Ce-Ujlwj.js} +2 -2
  4. package/dist/assets/JsonOutput-CfEBzaqW.js +11 -0
  5. package/dist/assets/{LazyAnyLanguageCodeMirror-B4fCknq1.js → LazyAnyLanguageCodeMirror-DoxWX_Wp.js} +2 -2
  6. package/dist/assets/MarimoErrorOutput-Bh6o9zle.js +10 -0
  7. package/dist/assets/{RenderHTML-DX6i99Op.js → RenderHTML-COGZsXQV.js} +1 -1
  8. package/dist/assets/RunButton-DQ51plUv.js +1 -0
  9. package/dist/assets/__vite-browser-external-BynxqS4w.js +1 -0
  10. package/dist/assets/__vite-browser-external-DTOT9NDN.js +1 -0
  11. package/dist/assets/{add-cell-with-ai-BpD_790A.js → add-cell-with-ai-D7gtGUsg.js} +6 -6
  12. package/dist/assets/{add-connection-dialog-B1BXCjex.js → add-connection-dialog-CpMCc97S.js} +1 -1
  13. package/dist/assets/{agent-panel-CDFUc1uu.js → agent-panel-BSFYqJ9B.js} +3 -3
  14. package/dist/assets/ai-model-dropdown-D575Vid8.js +13 -0
  15. package/dist/assets/alert-dialog-8YiYLrfr.js +1 -0
  16. package/dist/assets/any-language-editor-BWmz0P_C.js +1 -0
  17. package/dist/assets/{app-config-button-AdV-QDD7.js → app-config-button-Cfcg_5Bz.js} +1 -1
  18. package/dist/assets/arrays-0R2reF2y.js +1 -0
  19. package/dist/assets/{blob-BG8yoQmx.js → blob-CszMu60N.js} +1 -1
  20. package/dist/assets/{button-COI-NGrx.js → button-Cl7haVNh.js} +1 -1
  21. package/dist/assets/{cache-panel-DTY1dqvi.js → cache-panel-DuBsA6oO.js} +1 -1
  22. package/dist/assets/{capabilities-BJvf-MD4.js → capabilities-DcB-Dd0T.js} +1 -1
  23. package/dist/assets/cell-editor-BLpOlLnM.js +42 -0
  24. package/dist/assets/cell-link-Cj0gGaMV.js +1 -0
  25. package/dist/assets/cells-SvbLY10y.js +220 -0
  26. package/dist/assets/{chat-display-DhgI2MS9.js → chat-display-D43_g3TX.js} +1 -1
  27. package/dist/assets/chat-panel-B_HAJMwx.js +4 -0
  28. package/dist/assets/chat-ui-BEcYCphu.js +4 -0
  29. package/dist/assets/circle-B0vOU-Dh.js +1 -0
  30. package/dist/assets/{code-visibility-BJImdpDi.js → code-visibility-D1Wf7-vD.js} +1 -1
  31. package/dist/assets/column-preview-CrzaJKk8.js +1 -0
  32. package/dist/assets/command-palette-Bm9yIVcD.js +1 -0
  33. package/dist/assets/{common-Sl3uthjg.js → common-BJyUxCzv.js} +1 -1
  34. package/dist/assets/{components-CskjK6xZ.js → components-9p_w2C6e.js} +1 -1
  35. package/dist/assets/components-B6gDDccL.js +1 -0
  36. package/dist/assets/{components-3cou6Mka.js → components-D1f_Cz4i.js} +1 -1
  37. package/dist/assets/config-7IjqNuVp.js +1 -0
  38. package/dist/assets/config-BC32AAdw.js +1 -0
  39. package/dist/assets/{config-CA1V-wjh.js → config-CXnC3MRv.js} +1 -1
  40. package/dist/assets/{copy-D8O_CYCG.js → copy-74EjuZso.js} +1 -1
  41. package/dist/assets/{copy-icon-BIiRXh_3.js → copy-icon-Craaocbu.js} +1 -1
  42. package/dist/assets/{createReducer-DgRODTW6.js → createReducer-B04CIO-e.js} +1 -1
  43. package/dist/assets/{datasource-DfbNb83e.js → datasource-C8KSVfSD.js} +1 -1
  44. package/dist/assets/{dates-CfmdTL6B.js → dates-CaPZVGtm.js} +1 -1
  45. package/dist/assets/{dependency-graph-panel-L5cBE5sl.js → dependency-graph-panel-BhmyJbUl.js} +3 -3
  46. package/dist/assets/dist-gaK_BThd.js +1 -0
  47. package/dist/assets/{documentation-panel-D7S8JIc2.js → documentation-panel-DcVG0Q0d.js} +1 -1
  48. package/dist/assets/download-Bx52VRE4.js +9 -0
  49. package/dist/assets/dropdown-menu-Cwi17BoX.js +1 -0
  50. package/dist/assets/edit-page-DUrwOHR-.js +9 -0
  51. package/dist/assets/{error-banner-COZ7SfYV.js → error-banner-m3mastc-.js} +1 -1
  52. package/dist/assets/{error-entries-CLnPDv7b.js → error-entries-11OuHzUF.js} +1 -1
  53. package/dist/assets/{error-panel-D2xrYLue.js → error-panel-XJy5S25F.js} +1 -1
  54. package/dist/assets/{es-Cqst59WX.js → es-Rg27gAEy.js} +1 -1
  55. package/dist/assets/{feedback-button-DzPunIqU.js → feedback-button-B7jh_B4w.js} +1 -1
  56. package/dist/assets/feedback-button-C-eKtxXE.js +1 -0
  57. package/dist/assets/file-explorer-panel-CnVxbF4A.js +1 -0
  58. package/dist/assets/{file-name-input-B4fRaQkI.js → file-name-input-BJuqjQqH.js} +2 -2
  59. package/dist/assets/floating-outline-8yyZ-MOJ.js +1 -0
  60. package/dist/assets/{formats-yZ06h1Pl.js → formats-CYyvbiZA.js} +1 -1
  61. package/dist/assets/{formatting-BqrP-n49.js → formatting-DW2u7HXI.js} +1 -1
  62. package/dist/assets/fullscreen-D-ldV-19.js +41 -0
  63. package/dist/assets/{gallery-page-BefvdsM7.js → gallery-page-DJvGHePf.js} +1 -1
  64. package/dist/assets/{ganttDiagram-EL5Y4UJY-2n7Ig5Jm.js → ganttDiagram-EL5Y4UJY-CYqkCfFG.js} +1 -1
  65. package/dist/assets/{glide-data-editor-C3eaYlXg.js → glide-data-editor-Cyc8DHZx.js} +2 -2
  66. package/dist/assets/{glide-utils-CIrAxdjv.js → glide-utils-DsTeJ7WA.js} +2 -2
  67. package/dist/assets/home-page-tRZJmSU6.js +1 -0
  68. package/dist/assets/{hooks-DzML9nez.js → hooks-CxGwybGw.js} +1 -1
  69. package/dist/assets/{html-to-image-DK9Zl1lU.js → html-to-image-BW0fcUS8.js} +1 -1
  70. package/dist/assets/index-CoxbqhGH.js +30 -0
  71. package/dist/assets/index-u08m0hQE.css +2 -0
  72. package/dist/assets/{input-BoBxR50a.js → input-DLA5Dhn3.js} +2 -2
  73. package/dist/assets/kiosk-mode-Cbxffiz9.js +1 -0
  74. package/dist/assets/{layout-Ck1RoL1B.js → layout-DvXJxBiL.js} +4 -4
  75. package/dist/assets/linear-Bma8J3iM.js +1 -0
  76. package/dist/assets/links-UwsB2RA1.js +1 -0
  77. package/dist/assets/{logs-panel-BRytaWaO.js → logs-panel-KtsY7TrR.js} +1 -1
  78. package/dist/assets/{markdown-renderer-DX0Xrrfy.js → markdown-renderer-DUgV7_fm.js} +1 -1
  79. package/dist/assets/menu-items-yD7IV3V4.js +1 -0
  80. package/dist/assets/{mermaid-C4EiVqcd.js → mermaid-DmekSy8J.js} +2 -2
  81. package/dist/assets/{name-cell-input-DlpTvPLa.js → name-cell-input-C3-Cj-Ek.js} +1 -1
  82. package/dist/assets/numbers-DJK15_4S.js +1 -0
  83. package/dist/assets/{objects-snlMn4yk.js → objects-rLPlQ3X_.js} +1 -1
  84. package/dist/assets/{once-Haz_uxEL.js → once-6KNTRW0u.js} +1 -1
  85. package/dist/assets/{outline-panel-B2WIpz-1.js → outline-panel-BzyY4JA6.js} +1 -1
  86. package/dist/assets/{package-data-dv4FBHxR.js → package-data-CKPmBDtg.js} +1 -1
  87. package/dist/assets/packages-panel-DxosbWko.js +1 -0
  88. package/dist/assets/{panel-accordion-state-BqSH5ttk.js → panel-accordion-state-BRbTib2B.js} +1 -1
  89. package/dist/assets/panels-Da3071Q4.js +1 -0
  90. package/dist/assets/precisionRound-BxlUs8G4.js +1 -0
  91. package/dist/assets/process-output-CSwx7rvo.js +1 -0
  92. package/dist/assets/{quadrantDiagram-AXDQQJYC-yBhCDI7E.js → quadrantDiagram-AXDQQJYC-D9KH5Zge.js} +1 -1
  93. package/dist/assets/react-vega-BTNeboR2.js +189 -0
  94. package/dist/assets/{readonly-code-display-MnMEoYW5.js → readonly-code-display-B9y9423Y.js} +1 -1
  95. package/dist/assets/{readonly-python-code-ggs4hAzC.js → readonly-python-code-Dgw6trlk.js} +1 -1
  96. package/dist/assets/{renderShortcut-C1JwYcqW.js → renderShortcut-Bxv9ZLnD.js} +1 -1
  97. package/dist/assets/{requests-lt0JK8Jt.js → requests-BIFR-h4M.js} +1 -1
  98. package/dist/assets/requests-DuLJN9Tf.js +1 -0
  99. package/dist/assets/{reveal-component-PiV1eQk9.js → reveal-component-B9A4wkG-.js} +37 -37
  100. package/dist/assets/run-page-C1MQ5b_A.js +1 -0
  101. package/dist/assets/{runs-BLp5jezv.js → runs-C8uOPNAW.js} +1 -1
  102. package/dist/assets/{runtime-details-state-CoL-p0MC.js → runtime-details-state-D_hOQL9T.js} +1 -1
  103. package/dist/assets/{save-worker-BbUSwZRG.js → save-worker-UwdGcNK5.js} +2 -1
  104. package/dist/assets/scratchpad-panel-CBCsPmMW.js +1 -0
  105. package/dist/assets/{scroll-to-bottom-button-1FyowpWV.js → scroll-to-bottom-button-CidjyqiH.js} +1 -1
  106. package/dist/assets/secrets-panel-ConQpchl.js +1 -0
  107. package/dist/assets/select-CgWQvW1F.js +1 -0
  108. package/dist/assets/{session-DqoWmb7X.js → session-BHgGkjDm.js} +1 -1
  109. package/dist/assets/session-panel-CiHCKPlx.js +1 -0
  110. package/dist/assets/snippets-panel-C0VDE_4i.js +1 -0
  111. package/dist/assets/{spec-BfSBcO5B.js → spec-DFtwOyXT.js} +1 -1
  112. package/dist/assets/state-BYETGwDZ.js +1 -0
  113. package/dist/assets/{state-uwRvJLb8.js → state-CZu2E2Lb.js} +1 -1
  114. package/dist/assets/{state-C3oDoyFe.js → state-ClBFtZxn.js} +1 -1
  115. package/dist/assets/{state-DxQnC_1I.js → state-CrLrURYG.js} +1 -1
  116. package/dist/assets/{state-DtGB7xeJ.js → state-DrraMmqz.js} +1 -1
  117. package/dist/assets/state-DvU2AiFx.js +107 -0
  118. package/dist/assets/{strings-B2vv-ENv.js → strings-3lNX212T.js} +1 -1
  119. package/dist/assets/{swiper-component-a07domrZ.js → swiper-component-BwGv-Pu1.js} +1 -1
  120. package/dist/assets/switch-D2gdC_ep.js +1 -0
  121. package/dist/assets/tabs-BLczoY3k.js +1 -0
  122. package/dist/assets/{terminal-wnSHYT8N.js → terminal-DLgGyFIP.js} +1 -1
  123. package/dist/assets/{time-MgToa8VM.js → time-DswSXBDk.js} +1 -1
  124. package/dist/assets/toast-trUlkvx3.js +1 -0
  125. package/dist/assets/tooltip-B5lAA_jn.js +1 -0
  126. package/dist/assets/tooltip-BiQdpXA0.js +61 -0
  127. package/dist/assets/tracing-C5d51Nmi.js +1 -0
  128. package/dist/assets/tracing-panel-B89R8dWE.js +2 -0
  129. package/dist/assets/{tree-actions-DnUokmva.js → tree-actions-Cph8FVzh.js} +1 -1
  130. package/dist/assets/use-restore-focus-DMSzeuUr.js +1 -0
  131. package/dist/assets/{useAddCell-IN24thff.js → useAddCell-VPkB72pn.js} +1 -1
  132. package/dist/assets/useAsyncData-qvoPYHtx.js +1 -0
  133. package/dist/assets/useBoolean-BkeUcn1n.js +1 -0
  134. package/dist/assets/useCellActionButton--zI3GFnt.js +1 -0
  135. package/dist/assets/useDebounce-DwTQ_1Pu.js +1 -0
  136. package/dist/assets/{useDeepCompareMemoize-DFIVN9dn.js → useDeepCompareMemoize-B2DVK2De.js} +1 -1
  137. package/dist/assets/useDelayElapsed-BlsDT4TV.js +1 -0
  138. package/dist/assets/{useDeleteCell-fQlM46Y2.js → useDeleteCell-CjewKeSr.js} +1 -1
  139. package/dist/assets/{useDependencyPanelTab-ImXYINm_.js → useDependencyPanelTab-Bt_ka-wl.js} +1 -1
  140. package/dist/assets/{useEvent-Dfcbgjhg.js → useEvent-BDjgaBtz.js} +1 -1
  141. package/dist/assets/{useEventListener-D9cftcHj.js → useEventListener-DPOTkksd.js} +1 -1
  142. package/dist/assets/{useIframeCapabilities-CwTkczVN.js → useIframeCapabilities-sHeZt_EA.js} +1 -1
  143. package/dist/assets/{useInstallPackage-BW06eter.js → useInstallPackage-BfNKN33w.js} +1 -1
  144. package/dist/assets/{useInterval--x3s1VpI.js → useInterval-BcdwHHB5.js} +1 -1
  145. package/dist/assets/useNotebookActions-DV2S6KaG.js +1 -0
  146. package/dist/assets/{useRunCells-DSJPXE-j.js → useRunCells-BupCZpzk.js} +1 -1
  147. package/dist/assets/{useSplitCell-DQyMF5gd.js → useSplitCell-CaLIR0TS.js} +1 -1
  148. package/dist/assets/useStateForKey-CGMJQT8x.js +1 -0
  149. package/dist/assets/{useTheme-n8Dj1zH7.js → useTheme-C2dnh2vM.js} +1 -1
  150. package/dist/assets/{utils-CaQYvazl.js → utils-DfElfBpz.js} +1 -1
  151. package/dist/assets/vega-component-DJf6ho22.js +1 -0
  152. package/dist/assets/{vega-embed-container-DJPNkHPb.js → vega-embed-container-DtArSJz1.js} +1 -1
  153. package/dist/assets/vega-loader.browser-Bu25TKax.js +6 -0
  154. package/dist/assets/worker-jm2VC7pS.js +159 -0
  155. package/dist/assets/{write-secret-modal-DzKAInL2.js → write-secret-modal-CBi2vNK_.js} +1 -1
  156. package/dist/assets/{xychartDiagram-S5SC5T6Z-KbGzG6-u.js → xychartDiagram-S5SC5T6Z-CeRnBS5e.js} +1 -1
  157. package/dist/index.html +81 -79
  158. package/package.json +14 -14
  159. package/src/__tests__/host-imports.test.ts +1 -0
  160. package/src/__tests__/module-entry.test.ts +32 -0
  161. package/src/__tests__/mount.test.ts +0 -20
  162. package/src/components/ai/__tests__/ai-model-dropdown.test.tsx +63 -0
  163. package/src/components/ai/ai-model-dropdown.tsx +11 -6
  164. package/src/components/app-config/ai-config.tsx +7 -2
  165. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  166. package/src/components/chat/acp/__tests__/session-tabs.test.tsx +43 -0
  167. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  168. package/src/components/chat/acp/blocks.tsx +3 -2
  169. package/src/components/chat/acp/session-tabs.tsx +8 -3
  170. package/src/components/chat/chat-components.tsx +2 -0
  171. package/src/components/chat/chat-panel.tsx +9 -8
  172. package/src/components/chat/chat-utils.ts +4 -8
  173. package/src/components/data-table/TableTopBar.tsx +2 -0
  174. package/src/components/data-table/__tests__/chart-spec-model.test.ts +195 -195
  175. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  176. package/src/components/data-table/__tests__/columns.test.tsx +105 -0
  177. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  178. package/src/components/data-table/__tests__/data-table.test.tsx +1 -0
  179. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  180. package/src/components/data-table/__tests__/export-actions.test.tsx +105 -0
  181. package/src/components/data-table/__tests__/keyboard-selection.test.tsx +188 -0
  182. package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
  183. package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
  184. package/src/components/data-table/cell-selection/feature.ts +2 -2
  185. package/src/components/data-table/cell-selection/types.ts +2 -2
  186. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
  187. package/src/components/data-table/charts/__tests__/spec-snapshot.test.ts +20 -20
  188. package/src/components/data-table/charts/__tests__/spec.test.ts +78 -52
  189. package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
  190. package/src/components/data-table/charts/chart-spec/spec.ts +68 -43
  191. package/src/components/data-table/charts/charts.tsx +40 -30
  192. package/src/components/data-table/charts/storage.ts +16 -5
  193. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  194. package/src/components/data-table/column-summary/chart-spec-model.tsx +23 -16
  195. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  196. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  197. package/src/components/data-table/columns.tsx +45 -46
  198. package/src/components/data-table/context-menu.tsx +36 -20
  199. package/src/components/data-table/data-table.tsx +13 -1
  200. package/src/components/data-table/date-popover.tsx +6 -4
  201. package/src/components/data-table/export-actions.tsx +76 -48
  202. package/src/components/data-table/range-focus/atoms.ts +4 -6
  203. package/src/components/data-table/range-focus/use-cell-range-selection.ts +65 -38
  204. package/src/components/data-table/range-focus/use-scroll-into-view.ts +21 -2
  205. package/src/components/data-table/renderers.tsx +16 -7
  206. package/src/components/data-table/row-viewer-panel/__tests__/row-viewer.test.tsx +24 -1
  207. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +4 -4
  208. package/src/components/data-table/search-highlight.tsx +79 -0
  209. package/src/components/data-table/types.ts +1 -0
  210. package/src/components/data-table/url-detector.tsx +49 -9
  211. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  212. package/src/components/datasources/__tests__/column-preview.test.tsx +63 -2
  213. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  214. package/src/components/datasources/column-preview.tsx +8 -2
  215. package/src/components/datasources/datasources.tsx +48 -35
  216. package/src/components/debugger/debugger-code.tsx +5 -0
  217. package/src/components/dependency-graph/dependency-graph-tree.tsx +8 -8
  218. package/src/components/dependency-graph/elements.ts +24 -21
  219. package/src/components/dependency-graph/panels.tsx +2 -1
  220. package/src/components/dependency-graph/utils/__tests__/layout.test.ts +18 -11
  221. package/src/components/editor/SortableCell.tsx +1 -3
  222. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  223. package/src/components/editor/__tests__/package-alert.test.tsx +22 -1
  224. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +24 -0
  225. package/src/components/editor/actions/pair-with-agent-modal.tsx +18 -15
  226. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  227. package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
  228. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  229. package/src/components/editor/cell/CreateCellButton.tsx +8 -10
  230. package/src/components/editor/cell/DeleteButton.tsx +1 -0
  231. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  232. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  233. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  234. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  235. package/src/components/editor/cell/collapse.tsx +5 -3
  236. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  237. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
  238. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  239. package/src/components/editor/chrome/panels/outline/__tests__/outline-keyboard.test.tsx +84 -0
  240. package/src/components/editor/chrome/panels/outline/floating-outline.tsx +39 -14
  241. package/src/components/editor/chrome/panels/outline/useActiveOutline.tsx +3 -2
  242. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  243. package/src/components/editor/chrome/panels/secrets-panel.tsx +1 -0
  244. package/src/components/editor/chrome/panels/snippets-panel.tsx +5 -1
  245. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +12 -18
  246. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +58 -1
  247. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  248. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
  249. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  250. package/src/components/editor/connections/components.tsx +1 -1
  251. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  252. package/src/components/editor/controls/__tests__/keyboard-shortcuts.test.tsx +61 -0
  253. package/src/components/editor/controls/command-palette.tsx +22 -10
  254. package/src/components/editor/controls/keyboard-shortcuts.tsx +14 -5
  255. package/src/components/editor/dynamic-favicon.tsx +21 -40
  256. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +12 -23
  257. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  258. package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +47 -0
  259. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  260. package/src/components/editor/file-tree/file-explorer.tsx +45 -8
  261. package/src/components/editor/file-tree/file-name-input.tsx +1 -0
  262. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  263. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  264. package/src/components/editor/header/status.tsx +28 -5
  265. package/src/components/editor/links/__tests__/cell-link.test.tsx +146 -0
  266. package/src/components/editor/links/cell-link.tsx +14 -5
  267. package/src/components/editor/navigation/focus-utils.ts +4 -4
  268. package/src/components/editor/notebook-banner.tsx +1 -0
  269. package/src/components/editor/notebook-cell.tsx +11 -2
  270. package/src/components/editor/output/MarimoTracebackOutput.tsx +19 -17
  271. package/src/components/editor/output/__tests__/traceback.test.tsx +27 -1
  272. package/src/components/editor/output/ansi-reduce.ts +33 -20
  273. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  274. package/src/components/editor/package-alert.tsx +14 -7
  275. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +12 -6
  276. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  277. package/src/components/home/components.tsx +17 -31
  278. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  279. package/src/components/icons/large-spinner.tsx +3 -14
  280. package/src/components/icons/spinner.tsx +1 -1
  281. package/src/components/mcp/mcp-status-indicator.tsx +1 -1
  282. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  283. package/src/components/pages/home-page.tsx +164 -91
  284. package/src/components/scratchpad/scratchpad.test.tsx +95 -0
  285. package/src/components/scratchpad/scratchpad.tsx +16 -3
  286. package/src/components/slides/reveal-component.tsx +4 -2
  287. package/src/components/slides/slide-cell-view.tsx +1 -1
  288. package/src/components/slides/slide-form.tsx +1 -0
  289. package/src/components/tracing/tracing-keyboard.test.tsx +85 -0
  290. package/src/components/tracing/tracing.tsx +45 -41
  291. package/src/components/ui/__tests__/draggable-popover.test.tsx +24 -0
  292. package/src/components/ui/alert-dialog.tsx +1 -1
  293. package/src/components/ui/draggable-popover.tsx +24 -2
  294. package/src/core/ai/context/providers/datasource.ts +6 -2
  295. package/src/core/ai/tools/base.ts +16 -7
  296. package/src/core/ai/tools/edit-notebook-tool.ts +47 -45
  297. package/src/core/ai/tools/registry.ts +13 -8
  298. package/src/core/ai/tools/sample-tool.ts +6 -7
  299. package/src/core/attachments/state.ts +5 -8
  300. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -0
  301. package/src/core/cells/__tests__/cell.test.ts +39 -0
  302. package/src/core/cells/__tests__/cells.test.ts +3 -1
  303. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
  304. package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
  305. package/src/core/cells/__tests__/session.test.ts +117 -36
  306. package/src/core/cells/cell.ts +14 -3
  307. package/src/core/cells/cells.ts +3 -1
  308. package/src/core/cells/pending-delete-service.ts +2 -6
  309. package/src/core/cells/session.ts +1 -1
  310. package/src/core/cells/types.ts +10 -1
  311. package/src/core/codemirror/__tests__/setup.test.ts +29 -29
  312. package/src/core/codemirror/cells/__tests__/extensions.test.ts +15 -0
  313. package/src/core/codemirror/cells/extensions.ts +7 -3
  314. package/src/core/codemirror/keymaps/vim.ts +2 -6
  315. package/src/core/codemirror/language/__tests__/sql.test.ts +22 -20
  316. package/src/core/codemirror/language/extension.ts +8 -8
  317. package/src/core/codemirror/language/languages/markdown.ts +8 -6
  318. package/src/core/codemirror/language/languages/python.ts +16 -10
  319. package/src/core/codemirror/language/languages/sql/sql.ts +9 -7
  320. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  321. package/src/core/codemirror/language/types.ts +7 -7
  322. package/src/core/codemirror/rtc/extension.ts +24 -12
  323. package/src/core/codemirror/rtc/loro/awareness.ts +34 -19
  324. package/src/core/codemirror/rtc/loro/sync.ts +1 -0
  325. package/src/core/codemirror/utils.ts +1 -0
  326. package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
  327. package/src/core/datasets/data-source-discovery.ts +4 -6
  328. package/src/core/document/__tests__/codemirror.test.ts +485 -0
  329. package/src/core/document/__tests__/draft.test.ts +322 -0
  330. package/src/core/document/__tests__/fixtures.ts +92 -0
  331. package/src/core/document/__tests__/hooks.test.tsx +386 -0
  332. package/src/core/document/__tests__/lifecycle-scenarios.test.ts +61 -0
  333. package/src/core/document/__tests__/model.test.ts +191 -0
  334. package/src/core/document/__tests__/protocol-fixtures.ts +38 -0
  335. package/src/core/document/__tests__/recovery-scenarios.test.ts +230 -0
  336. package/src/core/document/__tests__/scenario-fixture.ts +43 -0
  337. package/src/core/document/__tests__/scenarios.test.ts +205 -0
  338. package/src/core/document/__tests__/state.test.ts +438 -0
  339. package/src/core/document/__tests__/submission.test.ts +203 -0
  340. package/src/core/document/__tests__/test-host.test.ts +92 -0
  341. package/src/core/document/__tests__/test-host.ts +247 -0
  342. package/src/core/document/__tests__/test-notebook.ts +149 -0
  343. package/src/core/document/__tests__/user.ts +61 -0
  344. package/src/core/document/codemirror.ts +311 -0
  345. package/src/core/document/draft.ts +472 -0
  346. package/src/core/document/hooks.tsx +118 -0
  347. package/src/core/document/index.ts +15 -0
  348. package/src/core/document/model.ts +55 -0
  349. package/src/core/document/select.ts +41 -0
  350. package/src/core/document/state.ts +374 -0
  351. package/src/core/document/sync.ts +369 -0
  352. package/src/core/host/AGENTS.md +8 -2
  353. package/src/core/host/__tests__/attachments.test.ts +56 -0
  354. package/src/core/host/__tests__/document.test.ts +126 -0
  355. package/src/core/host/__tests__/fixtures.ts +71 -0
  356. package/src/core/host/__tests__/replica.test.ts +550 -0
  357. package/src/core/host/__tests__/static.test.ts +169 -0
  358. package/src/core/host/__tests__/wasm.test.ts +162 -0
  359. package/src/core/host/backend.ts +19 -0
  360. package/src/core/host/connection.ts +1 -40
  361. package/src/core/host/index.ts +14 -0
  362. package/src/core/host/replica.ts +286 -0
  363. package/src/core/host/resources/attachments.ts +22 -0
  364. package/src/core/host/resources/document.ts +101 -0
  365. package/src/core/host/static.ts +72 -0
  366. package/src/core/host/wasm.ts +102 -0
  367. package/src/core/hotkeys/actions.ts +2 -4
  368. package/src/core/islands/__tests__/bootstrap.test.ts +0 -26
  369. package/src/core/islands/bootstrap.ts +0 -1
  370. package/src/core/islands/components/__tests__/IslandControls.test.tsx +7 -5
  371. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  372. package/src/core/network/connection.ts +4 -2
  373. package/src/core/network/useConnectionNotice.ts +8 -2
  374. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  375. package/src/core/runtime/__tests__/runtime.test.ts +32 -58
  376. package/src/core/runtime/runtime.ts +38 -42
  377. package/src/core/runtime/types.ts +0 -2
  378. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  379. package/src/core/state/jotai.ts +1 -1
  380. package/src/core/static/__tests__/files.test.ts +38 -10
  381. package/src/core/static/files.ts +2 -0
  382. package/src/core/wasm/bridge.ts +38 -10
  383. package/src/core/wasm/store.ts +3 -3
  384. package/src/core/wasm/worker/bootstrap.ts +2 -1
  385. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  386. package/src/core/wasm/worker/types.ts +11 -0
  387. package/src/core/wasm/worker/worker.ts +48 -0
  388. package/src/core/websocket/__tests__/backend.test.ts +200 -0
  389. package/src/core/websocket/__tests__/connection-state.test.ts +0 -12
  390. package/src/core/websocket/__tests__/useNotebookReplica.hook.test.tsx +141 -0
  391. package/src/core/websocket/__tests__/useNotebookSession.hook.test.tsx +154 -428
  392. package/src/core/websocket/backend.ts +106 -0
  393. package/src/core/websocket/connection-state.ts +1 -11
  394. package/src/core/websocket/state.ts +7 -0
  395. package/src/core/websocket/transports/__tests__/notebook.test.ts +445 -0
  396. package/src/core/websocket/transports/notebook.ts +190 -0
  397. package/src/core/websocket/types.ts +2 -2
  398. package/src/core/websocket/useNotebookReplica.ts +69 -0
  399. package/src/core/websocket/useNotebookSession.tsx +10 -22
  400. package/src/css/globals.css +5 -0
  401. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  402. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  403. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  404. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  405. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  406. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  407. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  408. package/src/hooks/useAsyncData.ts +3 -3
  409. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  410. package/src/hooks/useDebounce.ts +6 -10
  411. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  412. package/src/hooks/useDelayElapsed.ts +14 -10
  413. package/src/hooks/useElapsedTime.ts +3 -3
  414. package/src/hooks/useEventListener.ts +1 -2
  415. package/src/hooks/useInputHistory.ts +4 -7
  416. package/src/hooks/useInternalStateWithSync.ts +6 -6
  417. package/src/hooks/useResizeObserver.ts +4 -7
  418. package/src/hooks/useScript.ts +67 -70
  419. package/src/hooks/useStateForKey.ts +93 -0
  420. package/src/mount.tsx +0 -4
  421. package/src/plugins/__tests__/plugin-object-schemas.test.ts +104 -0
  422. package/src/plugins/core/registerReactComponent.tsx +1 -1
  423. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  424. package/src/plugins/impl/DataEditorPlugin.tsx +1 -1
  425. package/src/plugins/impl/DataTablePlugin.tsx +29 -33
  426. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  427. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  428. package/src/plugins/impl/FormPlugin.tsx +1 -1
  429. package/src/plugins/impl/RangeSliderPlugin.tsx +0 -1
  430. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  431. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  432. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +117 -1
  433. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  434. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  435. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
  436. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  437. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  438. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  439. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  440. package/src/plugins/impl/chat/chat-ui.tsx +14 -11
  441. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  442. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  443. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  444. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  445. package/src/plugins/impl/data-explorer/components/column-summary.tsx +4 -2
  446. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  447. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -2
  448. package/src/plugins/impl/data-frames/__tests__/sidebar.test.tsx +160 -0
  449. package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +66 -66
  450. package/src/plugins/impl/data-frames/panel.tsx +43 -16
  451. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  452. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  453. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  454. package/src/plugins/impl/panel/utils.ts +17 -0
  455. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +2 -5
  456. package/src/plugins/impl/plotly/selection.ts +25 -19
  457. package/src/plugins/impl/vega/VegaPlugin.tsx +2 -3
  458. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  459. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  460. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  461. package/src/stories/button.stories.tsx +1 -0
  462. package/src/stories/editor.stories.tsx +24 -17
  463. package/src/stories/form-wrapper.stories.tsx +2 -2
  464. package/src/stories/select.stories.tsx +2 -2
  465. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  466. package/src/utils/__tests__/arrays.test.ts +35 -0
  467. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  468. package/src/utils/__tests__/numbers.test.ts +48 -1
  469. package/src/utils/__tests__/objects.test.ts +21 -0
  470. package/src/utils/arrays.ts +10 -0
  471. package/src/utils/blob.ts +1 -1
  472. package/src/utils/numbers.ts +28 -17
  473. package/src/utils/objects.ts +3 -7
  474. package/src/utils/once.ts +19 -19
  475. package/src/utils/tracer.ts +9 -5
  476. package/src/utils/zod-utils.ts +1 -3
  477. package/dist/assets/CellStatus-DVOHpdeJ.js +0 -1
  478. package/dist/assets/JsonOutput-Cx1iyibB.js +0 -11
  479. package/dist/assets/MarimoErrorOutput-s_5GOX8k.js +0 -10
  480. package/dist/assets/RunButton-BKoh0zYG.js +0 -1
  481. package/dist/assets/__vite-browser-external-CYO_P3S8.js +0 -1
  482. package/dist/assets/__vite-browser-external-FeDVGZU8.js +0 -1
  483. package/dist/assets/ai-model-dropdown-i1vfGeVi.js +0 -13
  484. package/dist/assets/alert-dialog-BaPGzeOK.js +0 -1
  485. package/dist/assets/any-language-editor-BdTWdMCI.js +0 -1
  486. package/dist/assets/arrays-BqUx5USx.js +0 -1
  487. package/dist/assets/cell-editor-BKJ06H5F.js +0 -21
  488. package/dist/assets/cell-link-8Auigi8P.js +0 -1
  489. package/dist/assets/cells-C6YGYGdi.js +0 -220
  490. package/dist/assets/chat-panel-Cau71Ygr.js +0 -4
  491. package/dist/assets/chat-ui-C5ql1Up-.js +0 -4
  492. package/dist/assets/circle-DXf-VWZG.js +0 -1
  493. package/dist/assets/circle-play-CYIb0qT6.js +0 -1
  494. package/dist/assets/column-preview-BZ_iNhtJ.js +0 -1
  495. package/dist/assets/command-palette-DDC3kBet.js +0 -1
  496. package/dist/assets/components-YBmtV0p6.js +0 -1
  497. package/dist/assets/config-DRIpOKqh.js +0 -1
  498. package/dist/assets/config-Dy28fDwn.js +0 -1
  499. package/dist/assets/dist-iaThVDkA.js +0 -1
  500. package/dist/assets/download-C03FOElq.js +0 -9
  501. package/dist/assets/dropdown-menu-C1rnl-0o.js +0 -1
  502. package/dist/assets/edit-page-D_Rg0gQO.js +0 -9
  503. package/dist/assets/feedback-button-Kla7i5RT.js +0 -1
  504. package/dist/assets/file-explorer-panel-Uk4_QIJQ.js +0 -1
  505. package/dist/assets/floating-outline-B5MaM1JS.js +0 -1
  506. package/dist/assets/fullscreen-B9tHcfpV.js +0 -41
  507. package/dist/assets/home-page-hmvKlc1w.js +0 -4
  508. package/dist/assets/index-6b4mf8Ai.css +0 -2
  509. package/dist/assets/index-ptK1gxmo.js +0 -30
  510. package/dist/assets/kiosk-mode-BZECfg9k.js +0 -1
  511. package/dist/assets/linear-0eij_aAG.js +0 -1
  512. package/dist/assets/link-NuFVR7G9.js +0 -1
  513. package/dist/assets/links-DkkL9DVa.js +0 -1
  514. package/dist/assets/menu-items-BrwnTgyK.js +0 -1
  515. package/dist/assets/numbers-Mk3-CcoU.js +0 -1
  516. package/dist/assets/packages-panel-CI5ff9b-.js +0 -1
  517. package/dist/assets/panels-BCcZ_yLK.js +0 -107
  518. package/dist/assets/precisionRound-Bq_nmf5u.js +0 -1
  519. package/dist/assets/process-output-f3C3PD0Z.js +0 -1
  520. package/dist/assets/react-vega-C3rYGtP6.js +0 -189
  521. package/dist/assets/requests-CgSY1X4a.js +0 -1
  522. package/dist/assets/run-page-AfZhtEh1.js +0 -1
  523. package/dist/assets/scratchpad-panel-D2rWg4k8.js +0 -1
  524. package/dist/assets/secrets-panel-D7KN4tVs.js +0 -1
  525. package/dist/assets/select-DTBn5eTb.js +0 -1
  526. package/dist/assets/session-panel-CnANhcTo.js +0 -1
  527. package/dist/assets/snippets-panel-ClGHtEKQ.js +0 -1
  528. package/dist/assets/state-BAJYRhmO.js +0 -1
  529. package/dist/assets/state-CbjBtkdd.js +0 -3
  530. package/dist/assets/switch-CTaiAjrz.js +0 -1
  531. package/dist/assets/tabs-BuQlZFbt.js +0 -1
  532. package/dist/assets/toast-BGzLGnvw.js +0 -1
  533. package/dist/assets/tooltip-C9TuY18o.js +0 -1
  534. package/dist/assets/tooltip-mT3CGest.js +0 -61
  535. package/dist/assets/tracing-Dz2rYOFL.js +0 -1
  536. package/dist/assets/tracing-panel-DqQY9V91.js +0 -2
  537. package/dist/assets/use-restore-focus-BxEGwIbk.js +0 -1
  538. package/dist/assets/useAsyncData-C7aJJz0Y.js +0 -1
  539. package/dist/assets/useBoolean-d0Tty94N.js +0 -1
  540. package/dist/assets/useCellActionButton-DmaFZ4oF.js +0 -1
  541. package/dist/assets/useDebounce-B_ccBZTp.js +0 -1
  542. package/dist/assets/useNotebookActions-DHt3s-ia.js +0 -1
  543. package/dist/assets/vega-component-IouMe13t.js +0 -1
  544. package/dist/assets/vega-loader.browser-CLRkNgm7.js +0 -6
  545. package/dist/assets/worker-DTQ7oH2A.js +0 -158
  546. package/dist/assets/ws-BXWY541V.js +0 -22
  547. package/src/core/host/__tests__/events.test.ts +0 -46
  548. package/src/core/host/__tests__/notebook-client.test.ts +0 -191
  549. package/src/core/host/__tests__/protocol.test.ts +0 -214
  550. package/src/core/host/events.ts +0 -60
  551. package/src/core/host/notebook-client.ts +0 -226
  552. package/src/core/host/protocol.ts +0 -1943
  553. package/src/core/state/__tests__/jotai.test.ts +0 -25
  554. package/src/core/websocket/__tests__/createConnectionTransport.test.ts +0 -49
  555. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +0 -140
  556. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  557. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -335
  558. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  559. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  560. package/src/core/websocket/transports/basic.ts +0 -102
  561. package/src/core/websocket/transports/create.ts +0 -31
  562. package/src/core/websocket/transports/sse.ts +0 -308
  563. package/src/core/websocket/transports/transport.ts +0 -50
  564. package/src/core/websocket/transports/ws.ts +0 -98
  565. package/src/core/websocket/useNotebookClient.ts +0 -80
@@ -1,17 +1,41 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import { render, waitFor } from "@testing-library/react";
3
+ import {
4
+ act,
5
+ render as renderComponent,
6
+ waitFor,
7
+ } from "@testing-library/react";
4
8
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
- import { useCellErrors } from "@/core/cells/cells";
9
+ import { Provider, createStore } from "jotai";
10
+ import type { ReactNode } from "react";
11
+ import { MockNotebook } from "@/__mocks__/notebook";
12
+ import { cellId } from "@/__tests__/branded";
13
+ import { notebookAtom } from "@/core/cells/cells";
6
14
  import { DynamicFavicon } from "../dynamic-favicon";
7
15
 
8
- // Mock useCellErrors hook
9
- vi.mock("@/core/cells/cells", () => ({
10
- useCellErrors: vi.fn(),
11
- }));
12
-
13
16
  describe("DynamicFavicon", () => {
14
17
  let favicon: HTMLLinkElement;
18
+ let store: ReturnType<typeof createStore>;
19
+ const id = cellId("cell");
20
+
21
+ function render(ui: ReactNode) {
22
+ return renderComponent(ui, {
23
+ wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
24
+ });
25
+ }
26
+
27
+ function setErrors() {
28
+ store.set(
29
+ notebookAtom,
30
+ MockNotebook.notebookStateWithErrors([
31
+ {
32
+ cellId: id,
33
+ cellName: "cell",
34
+ errorData: [MockNotebook.errors.syntax("Invalid syntax")],
35
+ },
36
+ ]),
37
+ );
38
+ }
15
39
 
16
40
  beforeEach(() => {
17
41
  document
@@ -27,15 +51,20 @@ describe("DynamicFavicon", () => {
27
51
  // Mock document.hasFocus
28
52
  vi.spyOn(document, "hasFocus").mockReturnValue(true);
29
53
 
30
- // Mock useCellErrors to return no errors by default
31
- (useCellErrors as ReturnType<typeof vi.fn>).mockReturnValue([]);
54
+ store = createStore();
55
+ store.set(
56
+ notebookAtom,
57
+ MockNotebook.notebookState({
58
+ cellData: { [id]: { code: "x = 1" } },
59
+ }),
60
+ );
32
61
  });
33
62
 
34
63
  afterEach(() => {
35
64
  document
36
65
  .querySelectorAll("link[rel~='icon']")
37
66
  .forEach((element) => element.remove());
38
- vi.clearAllMocks();
67
+ vi.restoreAllMocks();
39
68
  vi.useRealTimers();
40
69
  });
41
70
 
@@ -67,9 +96,7 @@ describe("DynamicFavicon", () => {
67
96
  });
68
97
 
69
98
  it("should show error favicon when run completes with errors", async () => {
70
- (useCellErrors as ReturnType<typeof vi.fn>).mockReturnValue([
71
- { error: "mock error" },
72
- ]);
99
+ setErrors();
73
100
 
74
101
  const { rerender } = render(<DynamicFavicon isRunning={true} />);
75
102
 
@@ -147,6 +174,72 @@ describe("DynamicFavicon", () => {
147
174
  });
148
175
  });
149
176
 
177
+ it("does not look up or rewrite the favicon when code changes", async () => {
178
+ const { rerender } = render(<DynamicFavicon isRunning={true} />);
179
+ await waitFor(() => expect(favicon.href).toContain("circle-play.ico"));
180
+ rerender(<DynamicFavicon isRunning={false} />);
181
+ await waitFor(() => expect(favicon.href).toContain("circle-check.ico"));
182
+ const lookup = vi.spyOn(document, "querySelector");
183
+ const write = vi.spyOn(favicon, "href", "set");
184
+
185
+ await act(async () => {
186
+ store.set(notebookAtom, (state) => ({
187
+ ...state,
188
+ cellData: {
189
+ ...state.cellData,
190
+ [id]: { ...state.cellData[id], code: "x = 2" },
191
+ },
192
+ }));
193
+ });
194
+
195
+ expect(
196
+ lookup.mock.calls.filter(
197
+ ([selector]) => selector === "link[rel~='icon']",
198
+ ),
199
+ ).toEqual([]);
200
+ expect(write).not.toHaveBeenCalled();
201
+ lookup.mockRestore();
202
+ write.mockRestore();
203
+ });
204
+
205
+ it("updates the favicon when the error count changes", async () => {
206
+ const { rerender } = render(<DynamicFavicon isRunning={true} />);
207
+ await waitFor(() => expect(favicon.href).toContain("circle-play.ico"));
208
+ rerender(<DynamicFavicon isRunning={false} />);
209
+ await waitFor(() => expect(favicon.href).toContain("circle-check.ico"));
210
+
211
+ act(() => setErrors());
212
+ await waitFor(() => expect(favicon.href).toContain("circle-x.ico"));
213
+
214
+ act(() =>
215
+ store.set(
216
+ notebookAtom,
217
+ MockNotebook.notebookState({ cellData: { [id]: {} } }),
218
+ ),
219
+ );
220
+ await waitFor(() => expect(favicon.href).toContain("circle-check.ico"));
221
+ });
222
+
223
+ it("keeps the completion icon until its own reset timer expires", async () => {
224
+ vi.useFakeTimers();
225
+ const { rerender } = render(<DynamicFavicon isRunning={true} />);
226
+ await act(() => vi.advanceTimersByTimeAsync(1000));
227
+ rerender(<DynamicFavicon isRunning={false} />);
228
+ await act(() => vi.advanceTimersByTimeAsync(2000));
229
+ expect(favicon.href).toContain("circle-check.ico");
230
+ await act(() => vi.advanceTimersByTimeAsync(1000));
231
+ expect(favicon.href).toContain("favicon.ico");
232
+ });
233
+
234
+ it("cancels the reset timer when unmounted", async () => {
235
+ vi.useFakeTimers();
236
+ const { unmount } = render(<DynamicFavicon isRunning={true} />);
237
+ await act(() => vi.advanceTimersByTimeAsync(0));
238
+ expect(vi.getTimerCount()).toBe(1);
239
+ unmount();
240
+ expect(vi.getTimerCount()).toBe(0);
241
+ });
242
+
150
243
  describe("notifications", () => {
151
244
  beforeEach(() => {
152
245
  vi.spyOn(document, "visibilityState", "get").mockReturnValue("hidden");
@@ -156,7 +249,7 @@ describe("DynamicFavicon", () => {
156
249
  global.Notification.permission = "granted";
157
250
  });
158
251
 
159
- it("should send success notification when run completes without errors", () => {
252
+ it("should send success notification when run completes without errors", async () => {
160
253
  // @ts-expect-error ok in tests
161
254
  global.Notification = vi
162
255
  .fn()
@@ -172,12 +265,11 @@ describe("DynamicFavicon", () => {
172
265
 
173
266
  const { rerender } = render(<DynamicFavicon isRunning={true} />);
174
267
  rerender(<DynamicFavicon isRunning={false} />);
268
+ await waitFor(() => expect(Notification).toHaveBeenCalledOnce());
175
269
  });
176
270
 
177
- it("should send error notification when run completes with errors", () => {
178
- (useCellErrors as ReturnType<typeof vi.fn>).mockReturnValue([
179
- { error: "mock error" },
180
- ]);
271
+ it("should send error notification when run completes with errors", async () => {
272
+ setErrors();
181
273
 
182
274
  // @ts-expect-error ok in tests
183
275
  global.Notification = vi
@@ -194,6 +286,7 @@ describe("DynamicFavicon", () => {
194
286
 
195
287
  const { rerender } = render(<DynamicFavicon isRunning={true} />);
196
288
  rerender(<DynamicFavicon isRunning={false} />);
289
+ await waitFor(() => expect(Notification).toHaveBeenCalledOnce());
197
290
  });
198
291
 
199
292
  it("should not send notification when document is visible", () => {
@@ -27,10 +27,12 @@ function mountPackageAlert({
27
27
  configuredManager,
28
28
  sandboxBackend,
29
29
  source = "kernel",
30
+ packages = ["polars"],
30
31
  }: {
31
32
  configuredManager: PackageManagerName;
32
33
  sandboxBackend: "uv" | "pixi" | null;
33
34
  source?: "kernel" | "server";
35
+ packages?: string[];
34
36
  }) {
35
37
  const store = createStore();
36
38
  const client = MockRequestClient.create();
@@ -55,7 +57,7 @@ function mountPackageAlert({
55
57
  packageAlert: {
56
58
  id: "missing-package",
57
59
  kind: "missing",
58
- packages: ["polars"],
60
+ packages,
59
61
  isolated: true,
60
62
  source,
61
63
  },
@@ -97,6 +99,25 @@ it("keeps the configured package manager selectable outside a sandbox", () => {
97
99
  expect(managerSelect).not.toBeDisabled();
98
100
  });
99
101
 
102
+ it("removes an extra absent from available choices through a keyboard menu item", async () => {
103
+ const client = mountPackageAlert({
104
+ configuredManager: "uv",
105
+ sandboxBackend: null,
106
+ packages: ["polars[dev]"],
107
+ });
108
+ fireEvent.keyDown(screen.getByRole("button", { name: "dev" }), {
109
+ key: "ArrowDown",
110
+ });
111
+ const remove = await screen.findByRole("menuitem", { name: "Remove dev" });
112
+ fireEvent.keyDown(remove, { key: "Enter" });
113
+ fireEvent.click(screen.getByTestId("install-packages-button"));
114
+ expect(client.sendInstallMissingPackages).toHaveBeenCalledExactlyOnceWith({
115
+ manager: "uv",
116
+ source: "kernel",
117
+ versions: { polars: "" },
118
+ });
119
+ });
120
+
100
121
  it("installs a selected version in a Pixi sandbox", () => {
101
122
  const client = mountPackageAlert({
102
123
  configuredManager: "uv",
@@ -314,6 +314,30 @@ describe("PairWithAgentModal", () => {
314
314
  expect(screen.getByRole("tabpanel")).not.toHaveTextContent("--notebook");
315
315
  });
316
316
 
317
+ it("reverifies the target when host mode is enabled again for the same Session", async () => {
318
+ await act(async () => {
319
+ render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
320
+ });
321
+ expect(screen.getByRole("tabpanel")).toHaveTextContent(NOTEBOOK_ID);
322
+ act(() =>
323
+ store.set(pairConfigAtom, { ...PAIR_CONFIG, hostEnabled: false }),
324
+ );
325
+
326
+ const pending = new Deferred<Response>();
327
+ vi.mocked(fetch).mockImplementation((url) =>
328
+ String(url).endsWith("/api/sessions")
329
+ ? pending.promise
330
+ : responseFor(url),
331
+ );
332
+ act(() => store.set(pairConfigAtom, PAIR_CONFIG));
333
+ expect(screen.getByRole("button", { name: "Copy command" })).toBeDisabled();
334
+ expect(screen.getByRole("tabpanel")).not.toHaveTextContent(NOTEBOOK_ID);
335
+
336
+ await act(async () => pending.resolve(await responseFor("/api/sessions")));
337
+ expect(screen.getByRole("button", { name: "Copy command" })).toBeEnabled();
338
+ expect(screen.getByRole("tabpanel")).toHaveTextContent(NOTEBOOK_ID);
339
+ });
340
+
317
341
  it("keeps copying disabled when Notebook verification fails", async () => {
318
342
  vi.mocked(fetch).mockImplementation((url) =>
319
343
  String(url).endsWith("/api/sessions")
@@ -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 {
@@ -53,21 +53,20 @@ export const PairWithAgentModal: React.FC<{
53
53
  const config = useAtomValue(pairConfigAtom);
54
54
  const hostEnabled = config?.hostEnabled ?? false;
55
55
  const stableSessionId = useAtomValue(stableSessionIdAtom);
56
+ const lookupIdentity = useMemo(
57
+ () => ({ hostEnabled, stableSessionId }),
58
+ [hostEnabled, stableSessionId],
59
+ );
56
60
  const [lookup, setLookup] = useState<
57
61
  {
58
- sessionId: string;
59
- } & (
60
- | { status: "ready"; notebookId: string | null }
61
- | { status: "loading" | "error" }
62
- )
62
+ identity: typeof lookupIdentity;
63
+ } & ({ status: "ready"; notebookId: string | null } | { status: "error" })
63
64
  >();
64
65
  useEffect(() => {
65
- if (!hostEnabled || !stableSessionId) {
66
- setLookup(undefined);
66
+ const { hostEnabled, stableSessionId: sessionId } = lookupIdentity;
67
+ if (!hostEnabled || !sessionId) {
67
68
  return;
68
69
  }
69
- const sessionId = stableSessionId;
70
- setLookup({ sessionId, status: "loading" });
71
70
  let cancelled = false;
72
71
  API.get<Record<string, { session_id: string; notebook_id: string | null }>>(
73
72
  "/sessions",
@@ -79,22 +78,26 @@ export const PairWithAgentModal: React.FC<{
79
78
  );
80
79
  setLookup(
81
80
  session
82
- ? { sessionId, status: "ready", notebookId: session.notebook_id }
83
- : { sessionId, status: "error" },
81
+ ? {
82
+ identity: lookupIdentity,
83
+ status: "ready",
84
+ notebookId: session.notebook_id,
85
+ }
86
+ : { identity: lookupIdentity, status: "error" },
84
87
  );
85
88
  }
86
89
  })
87
90
  .catch(() => {
88
91
  if (!cancelled) {
89
- setLookup({ sessionId, status: "error" });
92
+ setLookup({ identity: lookupIdentity, status: "error" });
90
93
  }
91
94
  });
92
95
  return () => {
93
96
  cancelled = true;
94
97
  };
95
- }, [hostEnabled, stableSessionId]);
98
+ }, [lookupIdentity]);
96
99
  const currentLookup =
97
- lookup?.sessionId === stableSessionId ? lookup : undefined;
100
+ lookup?.identity === lookupIdentity ? lookup : undefined;
98
101
  const verified = !hostEnabled || currentLookup?.status === "ready";
99
102
  const notebookId =
100
103
  hostEnabled && currentLookup?.status === "ready"
@@ -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();
@@ -408,6 +408,7 @@ export const AiCompletionEditor: React.FC<Props> = ({
408
408
  </div>
409
409
  </Tooltip>
410
410
  <Button
411
+ aria-label="Discard AI completion"
411
412
  data-testid="decline-completion-button"
412
413
  variant="text"
413
414
  size="icon"
@@ -51,6 +51,7 @@ export const StartupLogsAlert: React.FC = () => {
51
51
  {isRunning ? "Initializing..." : "Initialized"}
52
52
  </span>
53
53
  <Button
54
+ aria-label="Dismiss startup logs"
54
55
  variant="text"
55
56
  data-testid="remove-startup-logs-button"
56
57
  size="icon"
@@ -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;
@@ -37,6 +37,7 @@ export const DeleteButton = (props: {
37
37
  return (
38
38
  <Tooltip content={tooltipMsg} usePortal={false}>
39
39
  <Button
40
+ aria-label="Delete cell"
40
41
  variant="ghost"
41
42
  size="icon"
42
43
  onClick={onClick}
@@ -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
  }
@@ -67,10 +67,12 @@ export const CollapsedCellBanner: React.FC<{
67
67
  const states = getDescendantsStatus(notebook, cellId);
68
68
 
69
69
  return (
70
- <div
70
+ <button
71
+ type="button"
72
+ aria-label={`Expand ${count} collapsed ${count === 1 ? "cell" : "cells"}`}
71
73
  onClick={onClick}
72
74
  className={cn(
73
- "flex items-center justify-between w-[calc(100%-2rem)] h-9 bg-muted rounded-b mx-4 opacity-80 hover:opacity-100 cursor-pointer",
75
+ "flex items-center justify-between w-[calc(100%-2rem)] h-9 bg-muted rounded-b mx-4 opacity-80 hover:opacity-100 cursor-pointer focus-visible:outline focus-visible:outline-ring",
74
76
  )}
75
77
  >
76
78
  <Toolbar
@@ -102,7 +104,7 @@ export const CollapsedCellBanner: React.FC<{
102
104
  </>
103
105
  }
104
106
  />
105
- </div>
107
+ </button>
106
108
  );
107
109
  });
108
110
  CollapsedCellBanner.displayName = "CollapsedCellBanner";
@@ -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
  }