@marimo-team/islands 0.25.3-dev10 → 0.25.3-dev101

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 (345) hide show
  1. package/dist/{ConnectedDataExplorerComponent-CNMliVzw.js → ConnectedDataExplorerComponent-Dhf5EGs5.js} +14 -14
  2. package/dist/{ErrorBoundary-BsqSMK9Y.js → ErrorBoundary-kKLKZNGz.js} +8 -8
  3. package/dist/{abnfDiagram-VCTEODGH-D-Ez91gV.js → abnfDiagram-VCTEODGH-Di400kYu.js} +2 -2
  4. package/dist/any-language-editor-k2B8YI3Y.js +75 -0
  5. package/dist/{architectureDiagram-5GKGNRK7-CTpcNtO8.js → architectureDiagram-5GKGNRK7-BdTJcsNJ.js} +1 -1
  6. package/dist/{arrays-CN1djC4k.js → arrays-R6JsImiT.js} +8 -0
  7. package/dist/assets/__vite-browser-external-35VXYmnw.js +1 -0
  8. package/dist/assets/{worker-HPY9fbhz.js → worker-Dq8y-WM5.js} +2 -2
  9. package/dist/{button-CvjkwdSk.js → button-BxB3RDjN.js} +1 -1
  10. package/dist/{capabilities-CQ5IjhzL.js → capabilities-BgR0osfN.js} +2 -2
  11. package/dist/{chat-ui-0lKdOoQ3.js → chat-ui-BIgPxfPP.js} +1141 -1141
  12. package/dist/{check-EDwSwP89.js → check-P35dc2dH.js} +1 -1
  13. package/dist/{chunk-IMKFNOWR-DGxi-zeo.js → chunk-IMKFNOWR-mtD7aJBw.js} +1 -1
  14. package/dist/{chunk-SHT3W25Y-CV9J3rAB.js → chunk-SHT3W25Y-DybB1ShH.js} +1 -1
  15. package/dist/{chunk-SVP7TREG-BL-9Fqgg.js → chunk-SVP7TREG-DNhhdCex.js} +1 -1
  16. package/dist/{chunk-TICWLB2K-Cu_XMjDM.js → chunk-TICWLB2K-NhAQhlDf.js} +1 -1
  17. package/dist/{chunk-YOKDWASO-DPZk8vhS.js → chunk-YOKDWASO-DKplTrBY.js} +20 -20
  18. package/dist/{classDiagram-ZZMXUADV-BXXV13Hq.js → classDiagram-ZZMXUADV-GTL0l5xS.js} +2 -2
  19. package/dist/{classDiagram-v2-VYDZK3BY-B7FXJ1Cp.js → classDiagram-v2-VYDZK3BY-BWrM2ruI.js} +2 -2
  20. package/dist/{code-visibility-CXQvUXHT.js → code-visibility-CK84K0J7.js} +6 -6
  21. package/dist/{common-CuD-CAC-.js → common-DbG2Uufx.js} +5550 -5451
  22. package/dist/{copy-icon-Ft7t9bqz.js → copy-icon-CPoxUG7G.js} +3 -3
  23. package/dist/{cynefinDiagram-5FMLGOSQ-CH96rwba.js → cynefinDiagram-5FMLGOSQ-CX22wa7c.js} +1 -1
  24. package/dist/{dagre-GXQ25YYZ-BD3PmpFY.js → dagre-GXQ25YYZ-Df6Pp-cl.js} +1 -1
  25. package/dist/{data-grid-overlay-editor-DgeyYmC1.js → data-grid-overlay-editor-BggIiNWF.js} +5 -5
  26. package/dist/{data-grid-types-BH_r7g7l.js → data-grid-types-0PJ-9N5n.js} +1 -1
  27. package/dist/{diagram-S7CK7UJ4-BsSJJldB.js → diagram-S7CK7UJ4-CctU9kVx.js} +1 -1
  28. package/dist/{diagram-UQ7AKVKN-muQtNZc4.js → diagram-UQ7AKVKN-FVHjCF9j.js} +1 -1
  29. package/dist/{diagram-VX7I27RA-CdhuZAqR.js → diagram-VX7I27RA-BSDbwFFv.js} +1 -1
  30. package/dist/{diagram-Z3DM3KII-BD8mXjOj.js → diagram-Z3DM3KII-BX8kW7Nc.js} +1 -1
  31. package/dist/{dialog-CEBFZNyc.js → dialog-C1PcpCqX.js} +3 -3
  32. package/dist/{dist-DpBpA_n1.js → dist-Lm-D-Vr6.js} +3 -3
  33. package/dist/{dropdown-menu-CP_qCYHC.js → dropdown-menu-B_7xD2X4.js} +177 -169
  34. package/dist/{ebnfDiagram-PWID7BFC-BIN1sweE.js → ebnfDiagram-PWID7BFC-DtapdLet.js} +2 -2
  35. package/dist/{erDiagram-RLTQ6QDP-Ctp5cfoN.js → erDiagram-RLTQ6QDP-BAru9jHt.js} +1 -1
  36. package/dist/{error-banner-D-WCIiMt.js → error-banner-Sl2FlSJ4.js} +2 -2
  37. package/dist/{error-entries-DSvFWT_M.js → error-entries-TAO6kEhF.js} +797 -784
  38. package/dist/{feedback-button-DZMj9PF3.js → feedback-button-CRF9v3d1.js} +29 -29
  39. package/dist/{flowDiagram-HODETNUW-3EfLrlFb.js → flowDiagram-HODETNUW-CjaeFeea.js} +2 -2
  40. package/dist/{formats-CeOmU1MU.js → formats-V8ifA0Bk.js} +3 -3
  41. package/dist/{ganttDiagram-EL5Y4UJY-BQT16JSc.js → ganttDiagram-EL5Y4UJY-Dh_4cBJZ.js} +2 -2
  42. package/dist/{glide-data-editor-g_L8FLAC.js → glide-data-editor-DgZRBzBJ.js} +370 -368
  43. package/dist/{glide-utils-DJyqh0EM.js → glide-utils-Bt8_pBa2.js} +16 -16
  44. package/dist/{globals-CYGlmBXb.js → globals-Bxo0esfB.js} +3 -3
  45. package/dist/{highlighted-body-KPVGNVTW-zwjq21tf.js → highlighted-body-KPVGNVTW-3Ziwfz-V.js} +2 -2
  46. package/dist/{html-to-image-B8s5SgGm.js → html-to-image-CWdayGZU.js} +29 -29
  47. package/dist/{infoDiagram-27XIBGKW-DipQoHxl.js → infoDiagram-27XIBGKW-B1hmUggC.js} +1 -1
  48. package/dist/{ishikawaDiagram-5VMMS53U-DgFMjSGz.js → ishikawaDiagram-5VMMS53U-DUeT3t-d.js} +1 -1
  49. package/dist/{kanban-definition-UXKFOSKX-w1ACVjgl.js → kanban-definition-UXKFOSKX-DEUnNNgc.js} +1 -1
  50. package/dist/{label-BDKF3Afc.js → label-BRnUC0NX.js} +2 -2
  51. package/dist/{linear-Cnaq3Tfb.js → linear-DWERh7eI.js} +121 -73
  52. package/dist/{loader-BQOfjTIJ.js → loader-Bm8cy3e8.js} +12 -12
  53. package/dist/main.js +3236 -3019
  54. package/dist/{mermaid-CHo49Yv-.js → mermaid-B0fXpsNP.js} +38 -38
  55. package/dist/mermaid-HWGCJPDP-Du3-HPQA.js +2 -0
  56. package/dist/{mindmap-definition-YA3MSWOX-CrT0KSfX.js → mindmap-definition-YA3MSWOX-CPexlhDQ.js} +1 -1
  57. package/dist/{number-overlay-editor-9VRv-pdj.js → number-overlay-editor-CI5EhMRq.js} +3 -3
  58. package/dist/{objects-CzUgoMIF.js → objects-DCmMvZtn.js} +1 -3
  59. package/dist/{once-BtE-wKRL.js → once-eG8Vmvjd.js} +1 -1
  60. package/dist/{pegDiagram-XKGWAZYB-BqvywNJA.js → pegDiagram-XKGWAZYB-CMpp1OOQ.js} +2 -2
  61. package/dist/{pieDiagram-E7YTZNPT-BfxJ_XCc.js → pieDiagram-E7YTZNPT-DlMWg5t1.js} +1 -1
  62. package/dist/precisionRound-DhAzeRcm.js +49 -0
  63. package/dist/{process-output-CKRXyszb.js → process-output-DD-oSvZk.js} +4 -4
  64. package/dist/{quadrantDiagram-AXDQQJYC-Ccw0xMjN.js → quadrantDiagram-AXDQQJYC-Blrm7V7_.js} +1 -1
  65. package/dist/{railroadDiagram-O6MQD6OU-8vFLVTPu.js → railroadDiagram-O6MQD6OU-DBJfseeO.js} +2 -2
  66. package/dist/{react-vega-BED0DjiB.js → react-vega-C4T9Rb_w.js} +12284 -12270
  67. package/dist/{requirementDiagram-BXWQKSXE-a_MxGvxe.js → requirementDiagram-BXWQKSXE-BC7V639r.js} +1 -1
  68. package/dist/{reveal-component-C87mv9Mn.js → reveal-component-Y4-XPwJF.js} +23 -23
  69. package/dist/{select-BButFi-d.js → select-B0LRDH0g.js} +4 -4
  70. package/dist/{sequenceDiagram-WJ2MYXX4-CXFMnxGh.js → sequenceDiagram-WJ2MYXX4-CgesU6F-.js} +1 -1
  71. package/dist/{spec-BwarBpUJ.js → spec-CcT0slVc.js} +4 -4
  72. package/dist/{stateDiagram-D77RDMKH-DQeA_CwI.js → stateDiagram-D77RDMKH-lH_BUhs3.js} +2 -2
  73. package/dist/{stateDiagram-v2-MP3YSRHH-BxxQucm1.js → stateDiagram-v2-MP3YSRHH-8LL6FrBe.js} +2 -2
  74. package/dist/style.css +1 -1
  75. package/dist/{swimlanes-42K2YHIH-Do6L-cYg.js → swimlanes-42K2YHIH-Cuf9B9kE.js} +1 -1
  76. package/dist/{swimlanesDiagram-VR7AAH4N-DUzPvSVU.js → swimlanesDiagram-VR7AAH4N-BoMMtOPh.js} +2 -2
  77. package/dist/{swiper-component-DKntyn8p.js → swiper-component-DfhRvAFi.js} +2 -2
  78. package/dist/{time-BieKwdYS.js → time-BaMbJs4z.js} +18 -18
  79. package/dist/{timeline-definition-24CTP7MA-D3c46WM5.js → timeline-definition-24CTP7MA-yzzw4TF4.js} +1 -1
  80. package/dist/{toDate-vIXljnsr.js → toDate-DKH0_kMo.js} +29 -31
  81. package/dist/{toast-B9UMEi8u.js → toast-CFWpihJU.js} +4 -4
  82. package/dist/{tooltip-BO1REbqs.js → tooltip-CKO1pHJo.js} +2 -2
  83. package/dist/{use-restore-focus-BEM7sFWl.js → use-restore-focus-DBCITLeF.js} +1 -1
  84. package/dist/{useAsyncData-Deh5kszT.js → useAsyncData-D0mLJstx.js} +15 -14
  85. package/dist/{useDateFormatter-D8XruNGG.js → useDateFormatter-c_yJtkAe.js} +2 -2
  86. package/dist/{useDeepCompareMemoize-DXwzRINI.js → useDeepCompareMemoize-BpPWi3G3.js} +1 -1
  87. package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities-BkdeVdXC.js} +1 -1
  88. package/dist/{useLifecycle-j7D9uD1o.js → useLifecycle-Dum8WSXq.js} +3 -3
  89. package/dist/{useTheme-CRnjkku0.js → useTheme-CfeaVw5D.js} +6 -6
  90. package/dist/{vega-component-DTG6zxaY.js → vega-component-DgytCKrL.js} +147 -147
  91. package/dist/{vega-embed-container-BNHE18G9.js → vega-embed-container--LYZqguO.js} +2 -2
  92. package/dist/{vega-loader.browser-DNST6uVf.js → vega-loader.browser-B9Pz0EGP.js} +298 -507
  93. package/dist/{vennDiagram-4TSXK5OY-BSZD8JcZ.js → vennDiagram-4TSXK5OY-eJOrTqqC.js} +1 -1
  94. package/dist/{wardleyDiagram-VM6X3IG4-48cUWkfA.js → wardleyDiagram-VM6X3IG4-TILPvee6.js} +1 -1
  95. package/dist/{xychartDiagram-S5SC5T6Z-CswYgYeu.js → xychartDiagram-S5SC5T6Z-DUPvRwwJ.js} +2 -2
  96. package/package.json +7 -7
  97. package/src/__tests__/host-imports.test.ts +1 -0
  98. package/src/components/app-config/ai-config.tsx +7 -2
  99. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  100. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  101. package/src/components/chat/acp/blocks.tsx +2 -2
  102. package/src/components/chat/chat-components.tsx +2 -0
  103. package/src/components/chat/chat-panel.tsx +9 -8
  104. package/src/components/chat/chat-utils.ts +4 -8
  105. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  106. package/src/components/data-table/__tests__/columns.test.tsx +105 -0
  107. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  108. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  109. package/src/components/data-table/__tests__/export-actions.test.tsx +105 -0
  110. package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
  111. package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
  112. package/src/components/data-table/cell-selection/feature.ts +2 -2
  113. package/src/components/data-table/cell-selection/types.ts +2 -2
  114. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
  115. package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
  116. package/src/components/data-table/charts/charts.tsx +35 -25
  117. package/src/components/data-table/charts/storage.ts +16 -5
  118. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  119. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  120. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  121. package/src/components/data-table/columns.tsx +45 -46
  122. package/src/components/data-table/context-menu.tsx +36 -20
  123. package/src/components/data-table/data-table.tsx +7 -1
  124. package/src/components/data-table/date-popover.tsx +6 -4
  125. package/src/components/data-table/export-actions.tsx +76 -48
  126. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +2 -2
  127. package/src/components/data-table/search-highlight.tsx +79 -0
  128. package/src/components/data-table/types.ts +1 -0
  129. package/src/components/data-table/url-detector.tsx +49 -9
  130. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  131. package/src/components/datasources/__tests__/column-preview.test.tsx +63 -2
  132. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  133. package/src/components/datasources/column-preview.tsx +8 -2
  134. package/src/components/datasources/datasources.tsx +47 -35
  135. package/src/components/debugger/debugger-code.tsx +5 -0
  136. package/src/components/dependency-graph/panels.tsx +2 -1
  137. package/src/components/editor/SortableCell.tsx +1 -3
  138. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  139. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +24 -0
  140. package/src/components/editor/actions/pair-with-agent-modal.tsx +18 -15
  141. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  142. package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
  143. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  144. package/src/components/editor/cell/CreateCellButton.tsx +8 -10
  145. package/src/components/editor/cell/DeleteButton.tsx +1 -0
  146. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  147. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  148. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  149. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  150. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  151. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
  152. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  153. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  154. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +12 -18
  155. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +58 -1
  156. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  157. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
  158. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  159. package/src/components/editor/connections/components.tsx +1 -1
  160. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  161. package/src/components/editor/controls/command-palette.tsx +22 -10
  162. package/src/components/editor/dynamic-favicon.tsx +21 -40
  163. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +12 -23
  164. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  165. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  166. package/src/components/editor/file-tree/file-explorer.tsx +10 -5
  167. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  168. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  169. package/src/components/editor/notebook-banner.tsx +1 -0
  170. package/src/components/editor/output/MarimoTracebackOutput.tsx +2 -2
  171. package/src/components/editor/output/ansi-reduce.ts +6 -0
  172. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  173. package/src/components/editor/package-alert.tsx +6 -4
  174. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  175. package/src/components/home/components.tsx +17 -31
  176. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  177. package/src/components/icons/large-spinner.tsx +3 -14
  178. package/src/components/icons/spinner.tsx +1 -1
  179. package/src/components/mcp/mcp-status-indicator.tsx +1 -1
  180. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  181. package/src/components/pages/home-page.tsx +164 -91
  182. package/src/components/scratchpad/scratchpad.tsx +2 -2
  183. package/src/components/slides/reveal-component.tsx +4 -2
  184. package/src/components/slides/slide-cell-view.tsx +1 -1
  185. package/src/components/slides/slide-form.tsx +1 -0
  186. package/src/components/tracing/tracing.tsx +2 -2
  187. package/src/components/ui/alert-dialog.tsx +1 -1
  188. package/src/core/ai/context/providers/datasource.ts +6 -2
  189. package/src/core/attachments/state.ts +5 -8
  190. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -0
  191. package/src/core/cells/__tests__/cell.test.ts +39 -0
  192. package/src/core/cells/__tests__/cells.test.ts +3 -1
  193. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
  194. package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
  195. package/src/core/cells/cell.ts +14 -3
  196. package/src/core/cells/cells.ts +3 -1
  197. package/src/core/cells/pending-delete-service.ts +2 -6
  198. package/src/core/cells/session.ts +1 -1
  199. package/src/core/cells/types.ts +10 -1
  200. package/src/core/codemirror/__tests__/setup.test.ts +1 -1
  201. package/src/core/codemirror/keymaps/vim.ts +2 -6
  202. package/src/core/codemirror/language/languages/markdown.ts +1 -1
  203. package/src/core/codemirror/language/languages/python.ts +3 -3
  204. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  205. package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
  206. package/src/core/datasets/data-source-discovery.ts +4 -6
  207. package/src/core/document/__tests__/draft.test.ts +322 -0
  208. package/src/core/document/__tests__/fixtures.ts +92 -0
  209. package/src/core/document/__tests__/lifecycle-scenarios.test.ts +61 -0
  210. package/src/core/document/__tests__/model.test.ts +158 -0
  211. package/src/core/document/__tests__/protocol-fixtures.ts +38 -0
  212. package/src/core/document/__tests__/recovery-scenarios.test.ts +230 -0
  213. package/src/core/document/__tests__/scenario-fixture.ts +42 -0
  214. package/src/core/document/__tests__/scenarios.test.ts +205 -0
  215. package/src/core/document/__tests__/state.test.ts +446 -0
  216. package/src/core/document/__tests__/submission.test.ts +201 -0
  217. package/src/core/document/__tests__/test-host.test.ts +92 -0
  218. package/src/core/document/__tests__/test-host.ts +247 -0
  219. package/src/core/document/__tests__/test-notebook.ts +139 -0
  220. package/src/core/document/__tests__/user.ts +61 -0
  221. package/src/core/document/draft.ts +472 -0
  222. package/src/core/document/index.ts +10 -0
  223. package/src/core/document/model.ts +53 -0
  224. package/src/core/document/state.ts +401 -0
  225. package/src/core/document/sync.ts +359 -0
  226. package/src/core/edit-app.tsx +2 -2
  227. package/src/core/host/AGENTS.md +12 -2
  228. package/src/core/host/__tests__/attachments.test.ts +56 -0
  229. package/src/core/host/__tests__/document.test.ts +126 -0
  230. package/src/core/host/__tests__/fixtures.ts +41 -0
  231. package/src/core/host/__tests__/notebook-client.test.ts +570 -0
  232. package/src/core/host/connection.ts +42 -0
  233. package/src/core/host/notebook-client.ts +297 -0
  234. package/src/core/host/resources/attachments.ts +22 -0
  235. package/src/core/host/resources/document.ts +101 -0
  236. package/src/core/hotkeys/actions.ts +2 -4
  237. package/src/core/islands/__tests__/bootstrap.test.ts +0 -26
  238. package/src/core/islands/bootstrap.ts +0 -1
  239. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  240. package/src/core/network/connection.ts +4 -2
  241. package/src/core/network/useConnectionNotice.ts +8 -2
  242. package/src/core/run-app.tsx +2 -2
  243. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  244. package/src/core/runtime/__tests__/runtime.test.ts +18 -17
  245. package/src/core/runtime/runtime.ts +26 -30
  246. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  247. package/src/core/state/jotai.ts +1 -1
  248. package/src/core/static/files.ts +2 -0
  249. package/src/core/wasm/bridge.ts +2 -10
  250. package/src/core/wasm/store.ts +3 -3
  251. package/src/core/wasm/worker/bootstrap.ts +1 -1
  252. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  253. package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -29
  254. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +223 -0
  255. package/src/core/websocket/__tests__/useNotebookSession.hook.test.tsx +606 -0
  256. package/src/core/websocket/connection-state.ts +60 -0
  257. package/src/core/websocket/state.ts +7 -0
  258. package/src/core/websocket/transports/__tests__/notebook.test.ts +502 -0
  259. package/src/core/websocket/transports/notebook.ts +213 -0
  260. package/src/core/websocket/types.ts +9 -39
  261. package/src/core/websocket/useNotebookClient.ts +89 -0
  262. package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +53 -233
  263. package/src/css/globals.css +5 -0
  264. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  265. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  266. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  267. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  268. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  269. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  270. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  271. package/src/hooks/useAsyncData.ts +3 -3
  272. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  273. package/src/hooks/useDebounce.ts +5 -8
  274. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  275. package/src/hooks/useDelayElapsed.ts +14 -10
  276. package/src/hooks/useElapsedTime.ts +3 -3
  277. package/src/hooks/useInputHistory.ts +4 -7
  278. package/src/hooks/useInternalStateWithSync.ts +6 -6
  279. package/src/hooks/useResizeObserver.ts +4 -7
  280. package/src/hooks/useScript.ts +67 -70
  281. package/src/hooks/useStateForKey.ts +93 -0
  282. package/src/plugins/core/registerReactComponent.tsx +1 -1
  283. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  284. package/src/plugins/impl/DataTablePlugin.tsx +14 -15
  285. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  286. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  287. package/src/plugins/impl/FormPlugin.tsx +1 -1
  288. package/src/plugins/impl/RangeSliderPlugin.tsx +0 -1
  289. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  290. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  291. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +117 -1
  292. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  293. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  294. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
  295. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  296. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  297. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  298. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  299. package/src/plugins/impl/chat/chat-ui.tsx +12 -10
  300. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  301. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  302. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  303. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  304. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  305. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +0 -1
  306. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  307. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  308. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  309. package/src/plugins/impl/panel/utils.ts +17 -0
  310. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  311. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  312. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  313. package/src/stories/button.stories.tsx +1 -0
  314. package/src/stories/editor.stories.tsx +24 -17
  315. package/src/stories/form-wrapper.stories.tsx +2 -2
  316. package/src/stories/select.stories.tsx +2 -2
  317. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  318. package/src/utils/__tests__/arrays.test.ts +35 -0
  319. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  320. package/src/utils/__tests__/numbers.test.ts +48 -1
  321. package/src/utils/__tests__/objects.test.ts +21 -0
  322. package/src/utils/arrays.ts +10 -0
  323. package/src/utils/blob.ts +1 -1
  324. package/src/utils/numbers.ts +28 -17
  325. package/src/utils/objects.ts +3 -7
  326. package/src/utils/once.ts +19 -19
  327. package/src/utils/zod-utils.ts +1 -3
  328. package/dist/any-language-editor-DZ_YTVus.js +0 -76
  329. package/dist/assets/__vite-browser-external-BXYkiFsk.js +0 -1
  330. package/dist/mermaid-HWGCJPDP-ByCsZnYF.js +0 -2
  331. package/dist/precisionRound-DMIVQrt2.js +0 -97
  332. package/src/core/host/__tests__/protocol.test.ts +0 -214
  333. package/src/core/host/protocol.ts +0 -1943
  334. package/src/core/state/__tests__/jotai.test.ts +0 -25
  335. package/src/core/websocket/__tests__/useMarimoKernelConnection.hook.test.tsx +0 -789
  336. package/src/core/websocket/__tests__/useWebSocket.test.ts +0 -49
  337. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  338. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -338
  339. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  340. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  341. package/src/core/websocket/transports/basic.ts +0 -102
  342. package/src/core/websocket/transports/sse.ts +0 -308
  343. package/src/core/websocket/transports/transport.ts +0 -61
  344. package/src/core/websocket/transports/ws.ts +0 -98
  345. package/src/core/websocket/useWebSocket.tsx +0 -88
@@ -112,12 +112,16 @@ export const DatasetColumnPreview: React.FC<{
112
112
 
113
113
  const addDataframeChart = preview.chart_code &&
114
114
  table.source_type === "local" && (
115
- <AddDataframeChart chartCode={preview.chart_code} />
115
+ <AddDataframeChart
116
+ chartCode={preview.chart_code}
117
+ columnName={column.name}
118
+ />
116
119
  );
117
120
 
118
121
  const addSQLChart = table.source_type === "duckdb" && (
119
122
  <Tooltip content="Add SQL cell" delayDuration={400}>
120
123
  <Button
124
+ aria-label={`Add SQL cell for ${column.name}`}
121
125
  variant="outline"
122
126
  size="icon"
123
127
  className="z-10 bg-background absolute right-1 -top-1"
@@ -238,7 +242,8 @@ export function renderChart(chartSpec: string, theme: Theme) {
238
242
 
239
243
  export const AddDataframeChart: React.FC<{
240
244
  chartCode: string;
241
- }> = ({ chartCode }) => {
245
+ columnName: string;
246
+ }> = ({ chartCode, columnName }) => {
242
247
  const autoInstantiate = useAtomValue(autoInstantiateAtom);
243
248
  const lastFocusedCellId = useLastFocusedCellId();
244
249
  const { createNewCell } = useCellActions();
@@ -261,6 +266,7 @@ export const AddDataframeChart: React.FC<{
261
266
  return (
262
267
  <Tooltip content="Add chart to notebook" delayDuration={400}>
263
268
  <Button
269
+ aria-label={`Add chart to notebook for ${columnName}`}
264
270
  variant="outline"
265
271
  size="icon"
266
272
  className="z-10 bg-background absolute right-1 -top-0.5"
@@ -13,7 +13,7 @@ import {
13
13
  VisibilityToggleButton,
14
14
  } from "@/components/editor/file-tree/tree-actions";
15
15
  import { CopyClipboardIcon } from "@/components/icons/copy-icon";
16
- import { Button } from "@/components/ui/button";
16
+ import { Button, buttonVariants } from "@/components/ui/button";
17
17
  import { Command, CommandInput, CommandItem } from "@/components/ui/command";
18
18
  import { Tooltip } from "@/components/ui/tooltip";
19
19
  import { maybeAddMarimoImport } from "@/core/cells/add-missing-import";
@@ -702,7 +702,40 @@ const SchemaNode: React.FC<SchemaNodeProps> = (props) => {
702
702
  );
703
703
  };
704
704
 
705
- const TableList: React.FC<{
705
+ // Kept outside the component because the React Compiler does not yet
706
+ // support try/finally inside component bodies.
707
+ async function loadTableList(
708
+ sqlTableContext: SQLTableContext,
709
+ addTableList: ReturnType<typeof useDataSourceActions>["addTableList"],
710
+ setLoading: (loading: boolean) => void,
711
+ ) {
712
+ setLoading(true);
713
+ try {
714
+ const { engine, database, schema, schemaPath } = sqlTableContext;
715
+ const previewTableList = await PreviewSQLTableList.request({
716
+ engine: engine,
717
+ database: database,
718
+ schema: schema,
719
+ schemaPath: schemaPath ?? [],
720
+ });
721
+
722
+ if (!previewTableList?.tables) {
723
+ throw new Error("No tables available");
724
+ }
725
+
726
+ addTableList({
727
+ tables: previewTableList.tables,
728
+ sqlTableContext: sqlTableContext,
729
+ });
730
+ } finally {
731
+ setLoading(false);
732
+ }
733
+ }
734
+
735
+ /**
736
+ * This is exported for testing purposes only.
737
+ */
738
+ export const TableList: React.FC<{
706
739
  tables: DataTable[];
707
740
  sqlTableContext?: SQLTableContext;
708
741
  searchValue?: string;
@@ -731,26 +764,7 @@ const TableList: React.FC<{
731
764
  // resets tablesResolved to false).
732
765
  const { isPending, error } = useAsyncData(async () => {
733
766
  if (!tablesResolved && sqlTableContext) {
734
- setTablesLoading(true);
735
-
736
- const { engine, database, schema, schemaPath } = sqlTableContext;
737
- const previewTableList = await PreviewSQLTableList.request({
738
- engine: engine,
739
- database: database,
740
- schema: schema,
741
- schemaPath: schemaPath ?? [],
742
- });
743
-
744
- if (!previewTableList?.tables) {
745
- setTablesLoading(false);
746
- throw new Error("No tables available");
747
- }
748
-
749
- addTableList({
750
- tables: previewTableList.tables,
751
- sqlTableContext: sqlTableContext,
752
- });
753
- setTablesLoading(false);
767
+ await loadTableList(sqlTableContext, addTableList, setTablesLoading);
754
768
  }
755
769
  }, [tablesResolved, sqlTableContext]);
756
770
 
@@ -964,6 +978,7 @@ const DatasetTableItem: React.FC<{
964
978
  {renderRowsByColumns()}
965
979
  <Tooltip content="Add table to notebook" delayDuration={400}>
966
980
  <Button
981
+ aria-label={`Add table ${table.name} to notebook`}
967
982
  className="group-hover:inline-flex hidden"
968
983
  variant="text"
969
984
  size="icon"
@@ -1054,19 +1069,16 @@ const DatasetColumnItem: React.FC<{
1054
1069
  {isIndexed &&
1055
1070
  renderItemSubtext({ tooltipContent: "Indexed", content: "IDX" })}
1056
1071
  </div>
1057
- <Tooltip content="Copy column name" delayDuration={400}>
1058
- <Button
1059
- variant="text"
1060
- size="icon"
1061
- className="group-hover:opacity-100 opacity-0 hover:bg-muted text-muted-foreground hover:text-foreground"
1062
- >
1063
- <CopyClipboardIcon
1064
- tooltip={false}
1065
- value={column.name}
1066
- className="h-3 w-3"
1067
- />
1068
- </Button>
1069
- </Tooltip>
1072
+ <CopyClipboardIcon
1073
+ tooltip="Copy column name"
1074
+ value={column.name}
1075
+ ariaLabel={`Copy column name ${column.name}`}
1076
+ className="h-3 w-3"
1077
+ buttonClassName={cn(
1078
+ buttonVariants({ variant: "text", size: "icon" }),
1079
+ "group-hover:opacity-100 opacity-0 hover:bg-muted text-muted-foreground hover:text-foreground",
1080
+ )}
1081
+ />
1070
1082
  <span className="text-xs text-muted-foreground">
1071
1083
  {column.external_type}
1072
1084
  </span>
@@ -162,6 +162,7 @@ export const DebuggerControls: React.FC<{
162
162
  <div className="flex">
163
163
  <Tooltip content="Next line">
164
164
  <Button
165
+ aria-label="Next line"
165
166
  variant="text"
166
167
  size="icon"
167
168
  data-testid="debugger-next-button"
@@ -173,6 +174,7 @@ export const DebuggerControls: React.FC<{
173
174
  </Tooltip>
174
175
  <Tooltip content="Continue execution">
175
176
  <Button
177
+ aria-label="Continue execution"
176
178
  variant="text"
177
179
  size="icon"
178
180
  data-testid="debugger-continue-button"
@@ -187,6 +189,7 @@ export const DebuggerControls: React.FC<{
187
189
  </Tooltip>
188
190
  <Tooltip content="Print stack trace">
189
191
  <Button
192
+ aria-label="Print stack trace"
190
193
  variant="text"
191
194
  size="icon"
192
195
  data-testid="debugger-stack-button"
@@ -198,6 +201,7 @@ export const DebuggerControls: React.FC<{
198
201
  </Tooltip>
199
202
  <Tooltip content="Help">
200
203
  <Button
204
+ aria-label="Debugger help"
201
205
  variant="text"
202
206
  size="icon"
203
207
  data-testid="debugger-help-button"
@@ -210,6 +214,7 @@ export const DebuggerControls: React.FC<{
210
214
  {onClear && (
211
215
  <Tooltip content="Quit and clear">
212
216
  <Button
217
+ aria-label="Quit and clear debugger"
213
218
  variant="text"
214
219
  size="icon"
215
220
  data-testid="debugger-clear-button"
@@ -216,11 +216,12 @@ export const GraphSelectionPanel: React.FC<{
216
216
  </Button>
217
217
  </Tooltip>
218
218
  <ConnectionCellActionsDropdown cellId={selection.id}>
219
- <Button variant="ghost" size="icon">
219
+ <Button aria-label="Cell actions" variant="ghost" size="icon">
220
220
  <MoreVerticalIcon className="w-4 h-4" />
221
221
  </Button>
222
222
  </ConnectionCellActionsDropdown>
223
223
  <Button
224
+ aria-label="Close cell details"
224
225
  variant="text"
225
226
  size="icon"
226
227
  onClick={() => {
@@ -71,7 +71,6 @@ export const SortableCell = React.forwardRef(
71
71
  return (
72
72
  <SortableCellInternal
73
73
  ref={ref}
74
- cellId={cellId}
75
74
  canMoveX={canMoveX}
76
75
  {...props}
77
76
  attributes={attributes}
@@ -86,7 +85,7 @@ export const SortableCell = React.forwardRef(
86
85
  );
87
86
  SortableCell.displayName = "SortableCell";
88
87
 
89
- interface SortableCellInternalProps extends Props {
88
+ interface SortableCellInternalProps extends Omit<Props, "cellId"> {
90
89
  children: React.ReactNode;
91
90
  attributes: DraggableAttributes;
92
91
  listeners: SyntheticListenerMap | undefined;
@@ -99,7 +98,6 @@ interface SortableCellInternalProps extends Props {
99
98
  const SortableCellInternal = React.forwardRef(
100
99
  (
101
100
  {
102
- cellId,
103
101
  canMoveX,
104
102
  children,
105
103
  attributes,
@@ -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", () => {
@@ -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
+ });