@marimo-team/frontend 0.25.3-dev12 → 0.25.3-dev121

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 (500) hide show
  1. package/dist/assets/CellStatus-_Aw9_foI.js +1 -0
  2. package/dist/assets/{ConnectedDataExplorerComponent-TprnxZeJ.js → ConnectedDataExplorerComponent-BRgqbAsc.js} +1 -1
  3. package/dist/assets/{ErrorBoundary-CM13wrd8.js → ErrorBoundary-DxSzjvbx.js} +2 -2
  4. package/dist/assets/JsonOutput-B7pGj79O.js +11 -0
  5. package/dist/assets/{LazyAnyLanguageCodeMirror-B4fCknq1.js → LazyAnyLanguageCodeMirror-BPYE76jR.js} +2 -2
  6. package/dist/assets/MarimoErrorOutput-Bzo6Bkp5.js +10 -0
  7. package/dist/assets/{RenderHTML-DX6i99Op.js → RenderHTML-D6AC0IhC.js} +1 -1
  8. package/dist/assets/RunButton-BjYsPiKa.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-CFrR1iW4.js} +6 -6
  12. package/dist/assets/{add-connection-dialog-B1BXCjex.js → add-connection-dialog-CyNAB_W2.js} +1 -1
  13. package/dist/assets/{agent-panel-CDFUc1uu.js → agent-panel-VOrccb5q.js} +3 -3
  14. package/dist/assets/ai-model-dropdown-Bfxv_zvu.js +13 -0
  15. package/dist/assets/{alert-dialog-BaPGzeOK.js → alert-dialog-C-sWmKva.js} +1 -1
  16. package/dist/assets/any-language-editor-DUMe1UCk.js +1 -0
  17. package/dist/assets/{app-config-button-AdV-QDD7.js → app-config-button-BRcjv8A_.js} +1 -1
  18. package/dist/assets/arrays-0R2reF2y.js +1 -0
  19. package/dist/assets/{blob-BG8yoQmx.js → blob-DTYOX-m5.js} +1 -1
  20. package/dist/assets/{button-COI-NGrx.js → button-D8gVKwe-.js} +1 -1
  21. package/dist/assets/{cache-panel-DTY1dqvi.js → cache-panel-BV-c7ed0.js} +1 -1
  22. package/dist/assets/{capabilities-BJvf-MD4.js → capabilities-Pq1YNPlK.js} +1 -1
  23. package/dist/assets/cell-editor-eKgYK8Bd.js +42 -0
  24. package/dist/assets/{cell-link-8Auigi8P.js → cell-link-D3rkqgbU.js} +1 -1
  25. package/dist/assets/{cells-C6YGYGdi.js → cells-2HETnlvE.js} +18 -18
  26. package/dist/assets/{chat-display-DhgI2MS9.js → chat-display-BrT4VJJK.js} +1 -1
  27. package/dist/assets/chat-panel-qpPNEwxV.js +4 -0
  28. package/dist/assets/chat-ui-SAdJxg-k.js +4 -0
  29. package/dist/assets/{circle-DXf-VWZG.js → circle-B6D_6D8w.js} +1 -1
  30. package/dist/assets/{code-visibility-BJImdpDi.js → code-visibility-DYDagVy7.js} +1 -1
  31. package/dist/assets/column-preview-yI7-INkZ.js +1 -0
  32. package/dist/assets/command-palette-DF-B_YpW.js +1 -0
  33. package/dist/assets/{common-Sl3uthjg.js → common-DgXKwblm.js} +1 -1
  34. package/dist/assets/components-CBE8cOLK.js +1 -0
  35. package/dist/assets/{components-3cou6Mka.js → components-CuMhY4Sf.js} +1 -1
  36. package/dist/assets/{components-CskjK6xZ.js → components-DCo7mAWO.js} +1 -1
  37. package/dist/assets/config-CD1IuZFJ.js +1 -0
  38. package/dist/assets/{config-DRIpOKqh.js → config-DMz7oN0r.js} +1 -1
  39. package/dist/assets/{config-CA1V-wjh.js → config-DpY86_Yq.js} +1 -1
  40. package/dist/assets/{copy-D8O_CYCG.js → copy-KlhazlhB.js} +1 -1
  41. package/dist/assets/{copy-icon-BIiRXh_3.js → copy-icon-8-a5vovc.js} +1 -1
  42. package/dist/assets/{createReducer-DgRODTW6.js → createReducer-CaI_Oa0v.js} +1 -1
  43. package/dist/assets/{datasource-DfbNb83e.js → datasource-D9JMcEAP.js} +1 -1
  44. package/dist/assets/{dates-CfmdTL6B.js → dates-CQMwFko2.js} +1 -1
  45. package/dist/assets/{dependency-graph-panel-L5cBE5sl.js → dependency-graph-panel-BVJWPCvw.js} +3 -3
  46. package/dist/assets/{dist-iaThVDkA.js → dist-jlhMTKo_.js} +1 -1
  47. package/dist/assets/{documentation-panel-D7S8JIc2.js → documentation-panel--wI4XHso.js} +1 -1
  48. package/dist/assets/{download-C03FOElq.js → download-Dod42wfD.js} +1 -1
  49. package/dist/assets/{dropdown-menu-C1rnl-0o.js → dropdown-menu-DS6umAnH.js} +1 -1
  50. package/dist/assets/edit-page-Uj9YSRMX.js +9 -0
  51. package/dist/assets/{error-banner-COZ7SfYV.js → error-banner-B5kzVf_0.js} +1 -1
  52. package/dist/assets/{error-entries-CLnPDv7b.js → error-entries-D00nWhWE.js} +1 -1
  53. package/dist/assets/{error-panel-D2xrYLue.js → error-panel-CcN_1PWf.js} +1 -1
  54. package/dist/assets/{es-Cqst59WX.js → es-N9SHpmG7.js} +1 -1
  55. package/dist/assets/{feedback-button-DzPunIqU.js → feedback-button-ByGgqUsX.js} +1 -1
  56. package/dist/assets/feedback-button-tbWwJ_t6.js +1 -0
  57. package/dist/assets/file-explorer-panel-eTEqtMPW.js +1 -0
  58. package/dist/assets/{file-name-input-B4fRaQkI.js → file-name-input-D5SfTNWh.js} +1 -1
  59. package/dist/assets/{floating-outline-B5MaM1JS.js → floating-outline-B-k3P_AO.js} +1 -1
  60. package/dist/assets/{formats-yZ06h1Pl.js → formats-zylfwejq.js} +1 -1
  61. package/dist/assets/{formatting-BqrP-n49.js → formatting-uom1es4b.js} +1 -1
  62. package/dist/assets/{fullscreen-B9tHcfpV.js → fullscreen-BWTs8pYZ.js} +1 -1
  63. package/dist/assets/{gallery-page-BefvdsM7.js → gallery-page-BzbMhDHi.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-BMqhUrHI.js} +2 -2
  66. package/dist/assets/{glide-utils-CIrAxdjv.js → glide-utils-HFqhvWHI.js} +2 -2
  67. package/dist/assets/home-page-DJcYWiga.js +1 -0
  68. package/dist/assets/{hooks-DzML9nez.js → hooks-CGLb_CCz.js} +1 -1
  69. package/dist/assets/{html-to-image-DK9Zl1lU.js → html-to-image-D_0rd3_B.js} +1 -1
  70. package/dist/assets/index-BnhSnwvB.css +2 -0
  71. package/dist/assets/index-bSIORW11.js +30 -0
  72. package/dist/assets/{input-BoBxR50a.js → input-DmHMEZGh.js} +1 -1
  73. package/dist/assets/kiosk-mode-DFS6HopF.js +1 -0
  74. package/dist/assets/{layout-Ck1RoL1B.js → layout-C2XnLAfO.js} +5 -5
  75. package/dist/assets/linear-Bma8J3iM.js +1 -0
  76. package/dist/assets/links-DX2w97N9.js +1 -0
  77. package/dist/assets/{logs-panel-BRytaWaO.js → logs-panel-B3T1GFBk.js} +1 -1
  78. package/dist/assets/{markdown-renderer-DX0Xrrfy.js → markdown-renderer-BhiTvxtL.js} +1 -1
  79. package/dist/assets/{menu-items-BrwnTgyK.js → menu-items-BgUyNUpu.js} +1 -1
  80. package/dist/assets/{mermaid-C4EiVqcd.js → mermaid-BKXlWk4F.js} +2 -2
  81. package/dist/assets/{name-cell-input-DlpTvPLa.js → name-cell-input-CgAObR6N.js} +1 -1
  82. package/dist/assets/numbers-CsZVw3Dc.js +1 -0
  83. package/dist/assets/{objects-snlMn4yk.js → objects-rLPlQ3X_.js} +1 -1
  84. package/dist/assets/{once-Haz_uxEL.js → once-6KNTRW0u.js} +1 -1
  85. package/dist/assets/{outline-panel-B2WIpz-1.js → outline-panel-BhBb7Vqw.js} +1 -1
  86. package/dist/assets/{package-data-dv4FBHxR.js → package-data-CKPmBDtg.js} +1 -1
  87. package/dist/assets/packages-panel-Bt0xNpFl.js +1 -0
  88. package/dist/assets/{panel-accordion-state-BqSH5ttk.js → panel-accordion-state-ZsUdpAYn.js} +1 -1
  89. package/dist/assets/panels-DJghvIKo.js +1 -0
  90. package/dist/assets/precisionRound-BxlUs8G4.js +1 -0
  91. package/dist/assets/process-output-CeOMwE17.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-TvOC7ESE.js} +1 -1
  95. package/dist/assets/{readonly-python-code-ggs4hAzC.js → readonly-python-code-Cqkfdeyo.js} +1 -1
  96. package/dist/assets/{renderShortcut-C1JwYcqW.js → renderShortcut-rUwGqKgC.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-CwE6U9oJ.js} +37 -37
  100. package/dist/assets/run-page-CtonWTtJ.js +1 -0
  101. package/dist/assets/{runs-BLp5jezv.js → runs-C49hleBy.js} +1 -1
  102. package/dist/assets/{runtime-details-state-CoL-p0MC.js → runtime-details-state-CKqJSfSR.js} +1 -1
  103. package/dist/assets/{save-worker-BbUSwZRG.js → save-worker-UwdGcNK5.js} +2 -1
  104. package/dist/assets/scratchpad-panel-CwVBedfZ.js +1 -0
  105. package/dist/assets/{scroll-to-bottom-button-1FyowpWV.js → scroll-to-bottom-button-CIkON2WJ.js} +1 -1
  106. package/dist/assets/{secrets-panel-D7KN4tVs.js → secrets-panel-bcfEKxRx.js} +1 -1
  107. package/dist/assets/{select-DTBn5eTb.js → select-3CVGlYrC.js} +1 -1
  108. package/dist/assets/{session-DqoWmb7X.js → session-CMLIzZKw.js} +1 -1
  109. package/dist/assets/session-panel-CdHdKUCF.js +1 -0
  110. package/dist/assets/{snippets-panel-ClGHtEKQ.js → snippets-panel-DYLWOLYw.js} +1 -1
  111. package/dist/assets/{spec-BfSBcO5B.js → spec-DlH4Mp7d.js} +1 -1
  112. package/dist/assets/{state-uwRvJLb8.js → state-3mcyTS9q.js} +1 -1
  113. package/dist/assets/state-B_MCKzBL.js +1 -0
  114. package/dist/assets/{state-C3oDoyFe.js → state-C5WAeuTu.js} +1 -1
  115. package/dist/assets/{state-DxQnC_1I.js → state-Cv2JW3l9.js} +1 -1
  116. package/dist/assets/{state-DtGB7xeJ.js → state-DZN9vZ6z.js} +1 -1
  117. package/dist/assets/state-DgECPVFs.js +107 -0
  118. package/dist/assets/{strings-B2vv-ENv.js → strings-C25fhMR_.js} +1 -1
  119. package/dist/assets/{swiper-component-a07domrZ.js → swiper-component-CRGRy5_O.js} +1 -1
  120. package/dist/assets/{switch-CTaiAjrz.js → switch-DYjBfpNJ.js} +1 -1
  121. package/dist/assets/{tabs-BuQlZFbt.js → tabs-CY8E4r9U.js} +1 -1
  122. package/dist/assets/{terminal-wnSHYT8N.js → terminal-Ca-kubKb.js} +1 -1
  123. package/dist/assets/{time-MgToa8VM.js → time-DswSXBDk.js} +1 -1
  124. package/dist/assets/{toast-BGzLGnvw.js → toast-Gzxi-le_.js} +1 -1
  125. package/dist/assets/tooltip-Brya4Ghn.js +61 -0
  126. package/dist/assets/{tooltip-C9TuY18o.js → tooltip-CkGm8X7m.js} +1 -1
  127. package/dist/assets/tracing-B0J2ctTm.js +1 -0
  128. package/dist/assets/tracing-panel-C1OCpoeE.js +2 -0
  129. package/dist/assets/{tree-actions-DnUokmva.js → tree-actions-sjBxIglc.js} +1 -1
  130. package/dist/assets/{use-restore-focus-BxEGwIbk.js → use-restore-focus-DdS7Tl0F.js} +1 -1
  131. package/dist/assets/{useAddCell-IN24thff.js → useAddCell-CaXnFGki.js} +1 -1
  132. package/dist/assets/useAsyncData-qvoPYHtx.js +1 -0
  133. package/dist/assets/useBoolean-Dd2nWXFJ.js +1 -0
  134. package/dist/assets/useCellActionButton-CJ90CMEj.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-D9N85822.js} +1 -1
  139. package/dist/assets/{useDependencyPanelTab-ImXYINm_.js → useDependencyPanelTab-CzAE0cm-.js} +1 -1
  140. package/dist/assets/{useEvent-Dfcbgjhg.js → useEvent-BDjgaBtz.js} +1 -1
  141. package/dist/assets/{useEventListener-D9cftcHj.js → useEventListener-CjDeLqFC.js} +1 -1
  142. package/dist/assets/{useIframeCapabilities-CwTkczVN.js → useIframeCapabilities-Czr9e4uG.js} +1 -1
  143. package/dist/assets/{useInstallPackage-BW06eter.js → useInstallPackage-wcDq9hYT.js} +1 -1
  144. package/dist/assets/{useInterval--x3s1VpI.js → useInterval-DJdQmXnq.js} +1 -1
  145. package/dist/assets/useNotebookActions-CAMEQ38y.js +1 -0
  146. package/dist/assets/{useRunCells-DSJPXE-j.js → useRunCells-DGakx8hj.js} +1 -1
  147. package/dist/assets/{useSplitCell-DQyMF5gd.js → useSplitCell-AK5BKPGM.js} +1 -1
  148. package/dist/assets/useStateForKey-CGMJQT8x.js +1 -0
  149. package/dist/assets/{useTheme-n8Dj1zH7.js → useTheme-BAA27lh7.js} +1 -1
  150. package/dist/assets/{utils-CaQYvazl.js → utils-DfElfBpz.js} +1 -1
  151. package/dist/assets/vega-component-BeyxAJad.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-BEx-EUlh.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 +12 -12
  159. package/src/__tests__/host-imports.test.ts +1 -0
  160. package/src/__tests__/mount.test.ts +0 -20
  161. package/src/components/app-config/ai-config.tsx +7 -2
  162. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  163. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  164. package/src/components/chat/acp/blocks.tsx +2 -2
  165. package/src/components/chat/chat-components.tsx +2 -0
  166. package/src/components/chat/chat-panel.tsx +9 -8
  167. package/src/components/chat/chat-utils.ts +4 -8
  168. package/src/components/data-table/__tests__/chart-spec-model.test.ts +195 -195
  169. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  170. package/src/components/data-table/__tests__/columns.test.tsx +105 -0
  171. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  172. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  173. package/src/components/data-table/__tests__/export-actions.test.tsx +105 -0
  174. package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
  175. package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
  176. package/src/components/data-table/cell-selection/feature.ts +2 -2
  177. package/src/components/data-table/cell-selection/types.ts +2 -2
  178. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
  179. package/src/components/data-table/charts/__tests__/spec-snapshot.test.ts +20 -20
  180. package/src/components/data-table/charts/__tests__/spec.test.ts +78 -52
  181. package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
  182. package/src/components/data-table/charts/chart-spec/spec.ts +68 -43
  183. package/src/components/data-table/charts/charts.tsx +40 -30
  184. package/src/components/data-table/charts/storage.ts +16 -5
  185. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  186. package/src/components/data-table/column-summary/chart-spec-model.tsx +23 -16
  187. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  188. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  189. package/src/components/data-table/columns.tsx +45 -46
  190. package/src/components/data-table/context-menu.tsx +36 -20
  191. package/src/components/data-table/data-table.tsx +7 -1
  192. package/src/components/data-table/date-popover.tsx +6 -4
  193. package/src/components/data-table/export-actions.tsx +76 -48
  194. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +2 -2
  195. package/src/components/data-table/search-highlight.tsx +79 -0
  196. package/src/components/data-table/types.ts +1 -0
  197. package/src/components/data-table/url-detector.tsx +49 -9
  198. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  199. package/src/components/datasources/__tests__/column-preview.test.tsx +63 -2
  200. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  201. package/src/components/datasources/column-preview.tsx +8 -2
  202. package/src/components/datasources/datasources.tsx +47 -35
  203. package/src/components/debugger/debugger-code.tsx +5 -0
  204. package/src/components/dependency-graph/dependency-graph-tree.tsx +8 -8
  205. package/src/components/dependency-graph/elements.ts +24 -21
  206. package/src/components/dependency-graph/panels.tsx +2 -1
  207. package/src/components/dependency-graph/utils/__tests__/layout.test.ts +18 -11
  208. package/src/components/editor/SortableCell.tsx +1 -3
  209. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  210. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +24 -0
  211. package/src/components/editor/actions/pair-with-agent-modal.tsx +18 -15
  212. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  213. package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
  214. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  215. package/src/components/editor/cell/CreateCellButton.tsx +8 -10
  216. package/src/components/editor/cell/DeleteButton.tsx +1 -0
  217. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  218. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  219. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  220. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  221. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  222. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
  223. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  224. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  225. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +12 -18
  226. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +58 -1
  227. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  228. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
  229. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  230. package/src/components/editor/connections/components.tsx +1 -1
  231. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  232. package/src/components/editor/controls/command-palette.tsx +22 -10
  233. package/src/components/editor/dynamic-favicon.tsx +21 -40
  234. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +12 -23
  235. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  236. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  237. package/src/components/editor/file-tree/file-explorer.tsx +10 -5
  238. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  239. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  240. package/src/components/editor/notebook-banner.tsx +1 -0
  241. package/src/components/editor/output/MarimoTracebackOutput.tsx +2 -2
  242. package/src/components/editor/output/ansi-reduce.ts +33 -20
  243. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  244. package/src/components/editor/package-alert.tsx +6 -4
  245. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +12 -6
  246. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  247. package/src/components/home/components.tsx +17 -31
  248. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  249. package/src/components/icons/large-spinner.tsx +3 -14
  250. package/src/components/icons/spinner.tsx +1 -1
  251. package/src/components/mcp/mcp-status-indicator.tsx +1 -1
  252. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  253. package/src/components/pages/home-page.tsx +164 -91
  254. package/src/components/scratchpad/scratchpad.tsx +2 -2
  255. package/src/components/slides/reveal-component.tsx +4 -2
  256. package/src/components/slides/slide-cell-view.tsx +1 -1
  257. package/src/components/slides/slide-form.tsx +1 -0
  258. package/src/components/tracing/tracing.tsx +2 -2
  259. package/src/components/ui/alert-dialog.tsx +1 -1
  260. package/src/core/ai/context/providers/datasource.ts +6 -2
  261. package/src/core/ai/tools/base.ts +16 -7
  262. package/src/core/ai/tools/edit-notebook-tool.ts +47 -45
  263. package/src/core/ai/tools/registry.ts +13 -8
  264. package/src/core/ai/tools/sample-tool.ts +6 -7
  265. package/src/core/attachments/state.ts +5 -8
  266. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -0
  267. package/src/core/cells/__tests__/cell.test.ts +39 -0
  268. package/src/core/cells/__tests__/cells.test.ts +3 -1
  269. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
  270. package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
  271. package/src/core/cells/__tests__/session.test.ts +117 -36
  272. package/src/core/cells/cell.ts +14 -3
  273. package/src/core/cells/cells.ts +3 -1
  274. package/src/core/cells/pending-delete-service.ts +2 -6
  275. package/src/core/cells/session.ts +1 -1
  276. package/src/core/cells/types.ts +10 -1
  277. package/src/core/codemirror/__tests__/setup.test.ts +29 -29
  278. package/src/core/codemirror/cells/__tests__/extensions.test.ts +15 -0
  279. package/src/core/codemirror/cells/extensions.ts +7 -3
  280. package/src/core/codemirror/keymaps/vim.ts +2 -6
  281. package/src/core/codemirror/language/__tests__/sql.test.ts +22 -20
  282. package/src/core/codemirror/language/extension.ts +8 -8
  283. package/src/core/codemirror/language/languages/markdown.ts +8 -6
  284. package/src/core/codemirror/language/languages/python.ts +16 -10
  285. package/src/core/codemirror/language/languages/sql/sql.ts +9 -7
  286. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  287. package/src/core/codemirror/language/types.ts +7 -7
  288. package/src/core/codemirror/rtc/extension.ts +24 -12
  289. package/src/core/codemirror/rtc/loro/awareness.ts +34 -19
  290. package/src/core/codemirror/rtc/loro/sync.ts +1 -0
  291. package/src/core/codemirror/utils.ts +1 -0
  292. package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
  293. package/src/core/datasets/data-source-discovery.ts +4 -6
  294. package/src/core/document/__tests__/codemirror.test.ts +483 -0
  295. package/src/core/document/__tests__/draft.test.ts +322 -0
  296. package/src/core/document/__tests__/fixtures.ts +92 -0
  297. package/src/core/document/__tests__/hooks.test.tsx +386 -0
  298. package/src/core/document/__tests__/lifecycle-scenarios.test.ts +61 -0
  299. package/src/core/document/__tests__/model.test.ts +158 -0
  300. package/src/core/document/__tests__/protocol-fixtures.ts +38 -0
  301. package/src/core/document/__tests__/recovery-scenarios.test.ts +230 -0
  302. package/src/core/document/__tests__/scenario-fixture.ts +42 -0
  303. package/src/core/document/__tests__/scenarios.test.ts +205 -0
  304. package/src/core/document/__tests__/state.test.ts +438 -0
  305. package/src/core/document/__tests__/submission.test.ts +203 -0
  306. package/src/core/document/__tests__/test-host.test.ts +92 -0
  307. package/src/core/document/__tests__/test-host.ts +247 -0
  308. package/src/core/document/__tests__/test-notebook.ts +139 -0
  309. package/src/core/document/__tests__/user.ts +61 -0
  310. package/src/core/document/codemirror.ts +311 -0
  311. package/src/core/document/draft.ts +472 -0
  312. package/src/core/document/hooks.tsx +118 -0
  313. package/src/core/document/index.ts +22 -0
  314. package/src/core/document/model.ts +52 -0
  315. package/src/core/document/select.ts +41 -0
  316. package/src/core/document/state.ts +374 -0
  317. package/src/core/document/sync.ts +363 -0
  318. package/src/core/host/AGENTS.md +12 -2
  319. package/src/core/host/__tests__/attachments.test.ts +56 -0
  320. package/src/core/host/__tests__/document.test.ts +126 -0
  321. package/src/core/host/__tests__/fixtures.ts +41 -0
  322. package/src/core/host/__tests__/notebook-client.test.ts +511 -132
  323. package/src/core/host/__tests__/static.test.ts +167 -0
  324. package/src/core/host/__tests__/wasm.test.ts +152 -0
  325. package/src/core/host/backend.ts +10 -0
  326. package/src/core/host/connection.ts +1 -40
  327. package/src/core/host/notebook-client.ts +204 -133
  328. package/src/core/host/resources/attachments.ts +22 -0
  329. package/src/core/host/resources/document.ts +101 -0
  330. package/src/core/host/static.ts +70 -0
  331. package/src/core/host/wasm.ts +92 -0
  332. package/src/core/hotkeys/actions.ts +2 -4
  333. package/src/core/islands/__tests__/bootstrap.test.ts +0 -26
  334. package/src/core/islands/bootstrap.ts +0 -1
  335. package/src/core/islands/components/__tests__/IslandControls.test.tsx +7 -5
  336. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  337. package/src/core/network/connection.ts +4 -2
  338. package/src/core/network/useConnectionNotice.ts +8 -2
  339. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  340. package/src/core/runtime/__tests__/runtime.test.ts +15 -58
  341. package/src/core/runtime/runtime.ts +25 -42
  342. package/src/core/runtime/types.ts +0 -2
  343. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  344. package/src/core/state/jotai.ts +1 -1
  345. package/src/core/static/__tests__/files.test.ts +38 -10
  346. package/src/core/static/files.ts +2 -0
  347. package/src/core/wasm/bridge.ts +38 -10
  348. package/src/core/wasm/store.ts +3 -3
  349. package/src/core/wasm/worker/bootstrap.ts +2 -1
  350. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  351. package/src/core/wasm/worker/types.ts +11 -0
  352. package/src/core/wasm/worker/worker.ts +48 -0
  353. package/src/core/websocket/__tests__/connection-state.test.ts +0 -12
  354. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +153 -70
  355. package/src/core/websocket/__tests__/useNotebookSession.hook.test.tsx +151 -428
  356. package/src/core/websocket/connection-state.ts +0 -10
  357. package/src/core/websocket/state.ts +7 -0
  358. package/src/core/websocket/transports/__tests__/notebook.test.ts +502 -0
  359. package/src/core/websocket/transports/notebook.ts +213 -0
  360. package/src/core/websocket/useNotebookClient.ts +38 -29
  361. package/src/core/websocket/useNotebookSession.tsx +1 -14
  362. package/src/css/globals.css +5 -0
  363. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  364. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  365. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  366. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  367. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  368. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  369. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  370. package/src/hooks/useAsyncData.ts +3 -3
  371. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  372. package/src/hooks/useDebounce.ts +6 -10
  373. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  374. package/src/hooks/useDelayElapsed.ts +14 -10
  375. package/src/hooks/useElapsedTime.ts +3 -3
  376. package/src/hooks/useEventListener.ts +1 -2
  377. package/src/hooks/useInputHistory.ts +4 -7
  378. package/src/hooks/useInternalStateWithSync.ts +6 -6
  379. package/src/hooks/useResizeObserver.ts +4 -7
  380. package/src/hooks/useScript.ts +67 -70
  381. package/src/hooks/useStateForKey.ts +93 -0
  382. package/src/mount.tsx +0 -4
  383. package/src/plugins/__tests__/plugin-object-schemas.test.ts +104 -0
  384. package/src/plugins/core/registerReactComponent.tsx +1 -1
  385. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  386. package/src/plugins/impl/DataEditorPlugin.tsx +1 -1
  387. package/src/plugins/impl/DataTablePlugin.tsx +29 -33
  388. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  389. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  390. package/src/plugins/impl/FormPlugin.tsx +1 -1
  391. package/src/plugins/impl/RangeSliderPlugin.tsx +0 -1
  392. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  393. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  394. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +117 -1
  395. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  396. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  397. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
  398. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  399. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  400. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  401. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  402. package/src/plugins/impl/chat/chat-ui.tsx +12 -10
  403. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  404. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  405. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  406. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  407. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  408. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -2
  409. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  410. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  411. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  412. package/src/plugins/impl/panel/utils.ts +17 -0
  413. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +2 -5
  414. package/src/plugins/impl/plotly/selection.ts +25 -19
  415. package/src/plugins/impl/vega/VegaPlugin.tsx +2 -3
  416. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  417. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  418. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  419. package/src/stories/button.stories.tsx +1 -0
  420. package/src/stories/editor.stories.tsx +24 -17
  421. package/src/stories/form-wrapper.stories.tsx +2 -2
  422. package/src/stories/select.stories.tsx +2 -2
  423. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  424. package/src/utils/__tests__/arrays.test.ts +35 -0
  425. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  426. package/src/utils/__tests__/numbers.test.ts +48 -1
  427. package/src/utils/__tests__/objects.test.ts +21 -0
  428. package/src/utils/arrays.ts +10 -0
  429. package/src/utils/blob.ts +1 -1
  430. package/src/utils/numbers.ts +28 -17
  431. package/src/utils/objects.ts +3 -7
  432. package/src/utils/once.ts +19 -19
  433. package/src/utils/tracer.ts +9 -5
  434. package/src/utils/zod-utils.ts +1 -3
  435. package/dist/assets/CellStatus-DVOHpdeJ.js +0 -1
  436. package/dist/assets/JsonOutput-Cx1iyibB.js +0 -11
  437. package/dist/assets/MarimoErrorOutput-s_5GOX8k.js +0 -10
  438. package/dist/assets/RunButton-BKoh0zYG.js +0 -1
  439. package/dist/assets/__vite-browser-external-CYO_P3S8.js +0 -1
  440. package/dist/assets/__vite-browser-external-FeDVGZU8.js +0 -1
  441. package/dist/assets/ai-model-dropdown-i1vfGeVi.js +0 -13
  442. package/dist/assets/any-language-editor-BdTWdMCI.js +0 -1
  443. package/dist/assets/arrays-BqUx5USx.js +0 -1
  444. package/dist/assets/cell-editor-BKJ06H5F.js +0 -21
  445. package/dist/assets/chat-panel-Cau71Ygr.js +0 -4
  446. package/dist/assets/chat-ui-C5ql1Up-.js +0 -4
  447. package/dist/assets/circle-play-CYIb0qT6.js +0 -1
  448. package/dist/assets/column-preview-BZ_iNhtJ.js +0 -1
  449. package/dist/assets/command-palette-DDC3kBet.js +0 -1
  450. package/dist/assets/components-YBmtV0p6.js +0 -1
  451. package/dist/assets/config-Dy28fDwn.js +0 -1
  452. package/dist/assets/edit-page-D_Rg0gQO.js +0 -9
  453. package/dist/assets/feedback-button-Kla7i5RT.js +0 -1
  454. package/dist/assets/file-explorer-panel-Uk4_QIJQ.js +0 -1
  455. package/dist/assets/home-page-hmvKlc1w.js +0 -4
  456. package/dist/assets/index-6b4mf8Ai.css +0 -2
  457. package/dist/assets/index-ptK1gxmo.js +0 -30
  458. package/dist/assets/kiosk-mode-BZECfg9k.js +0 -1
  459. package/dist/assets/linear-0eij_aAG.js +0 -1
  460. package/dist/assets/link-NuFVR7G9.js +0 -1
  461. package/dist/assets/links-DkkL9DVa.js +0 -1
  462. package/dist/assets/numbers-Mk3-CcoU.js +0 -1
  463. package/dist/assets/packages-panel-CI5ff9b-.js +0 -1
  464. package/dist/assets/panels-BCcZ_yLK.js +0 -107
  465. package/dist/assets/precisionRound-Bq_nmf5u.js +0 -1
  466. package/dist/assets/process-output-f3C3PD0Z.js +0 -1
  467. package/dist/assets/react-vega-C3rYGtP6.js +0 -189
  468. package/dist/assets/requests-CgSY1X4a.js +0 -1
  469. package/dist/assets/run-page-AfZhtEh1.js +0 -1
  470. package/dist/assets/scratchpad-panel-D2rWg4k8.js +0 -1
  471. package/dist/assets/session-panel-CnANhcTo.js +0 -1
  472. package/dist/assets/state-BAJYRhmO.js +0 -1
  473. package/dist/assets/state-CbjBtkdd.js +0 -3
  474. package/dist/assets/tooltip-mT3CGest.js +0 -61
  475. package/dist/assets/tracing-Dz2rYOFL.js +0 -1
  476. package/dist/assets/tracing-panel-DqQY9V91.js +0 -2
  477. package/dist/assets/useAsyncData-C7aJJz0Y.js +0 -1
  478. package/dist/assets/useBoolean-d0Tty94N.js +0 -1
  479. package/dist/assets/useCellActionButton-DmaFZ4oF.js +0 -1
  480. package/dist/assets/useDebounce-B_ccBZTp.js +0 -1
  481. package/dist/assets/useNotebookActions-DHt3s-ia.js +0 -1
  482. package/dist/assets/vega-component-IouMe13t.js +0 -1
  483. package/dist/assets/vega-loader.browser-CLRkNgm7.js +0 -6
  484. package/dist/assets/worker-DTQ7oH2A.js +0 -158
  485. package/dist/assets/ws-BXWY541V.js +0 -22
  486. package/src/core/host/__tests__/events.test.ts +0 -46
  487. package/src/core/host/__tests__/protocol.test.ts +0 -214
  488. package/src/core/host/events.ts +0 -60
  489. package/src/core/host/protocol.ts +0 -1943
  490. package/src/core/state/__tests__/jotai.test.ts +0 -25
  491. package/src/core/websocket/__tests__/createConnectionTransport.test.ts +0 -49
  492. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  493. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -335
  494. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  495. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  496. package/src/core/websocket/transports/basic.ts +0 -102
  497. package/src/core/websocket/transports/create.ts +0 -31
  498. package/src/core/websocket/transports/sse.ts +0 -308
  499. package/src/core/websocket/transports/transport.ts +0 -50
  500. package/src/core/websocket/transports/ws.ts +0 -98
@@ -1,6 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import { useCallback, useEffect, useRef, useState } from "react";
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { useRequestClient } from "@/core/network/requests";
5
5
  import { useDebounce } from "@/hooks/useDebounce";
6
6
  import {
@@ -34,20 +34,29 @@ export function useFileSearch({
34
34
  }): { state: FileSearchState; refetch: () => void } {
35
35
  const { sendSearchFiles } = useRequestClient();
36
36
  const debouncedQuery = useDebounce(query, 300);
37
- const pending = useRef<Promise<void>>(Promise.resolve());
37
+ // Nullable so the ref is not built on every render
38
+ const pending = useRef<Promise<void> | null>(null);
38
39
  const [revision, setRevision] = useState(0);
39
40
  const refetch = useCallback(() => setRevision((value) => value + 1), []);
40
- const [result, setResult] = useState<FileSearchState>({ status: "idle" });
41
+ const requestIdentity = useMemo(
42
+ () => ({ query, tree, showHiddenFiles, revision, sendSearchFiles }),
43
+ [query, tree, showHiddenFiles, revision, sendSearchFiles],
44
+ );
45
+ const [result, setResult] = useState<{
46
+ requestIdentity: typeof requestIdentity;
47
+ state: FileSearchState;
48
+ }>();
41
49
 
42
50
  useEffect(() => {
51
+ // Read inputs through requestIdentity so it alone drives new requests
52
+ const { query, tree, showHiddenFiles, sendSearchFiles } = requestIdentity;
43
53
  let cancelled = false;
44
54
  if (!query || query !== debouncedQuery) {
45
55
  return;
46
56
  }
47
- setResult({ status: "loading", query });
48
57
  // Let the in-flight server scan finish before starting another. Superseded
49
58
  // queued queries and remaining roots are skipped, keeping scan work bounded.
50
- const request = pending.current.then(async () => {
59
+ const request = (pending.current ?? Promise.resolve()).then(async () => {
51
60
  const filesByPath = new Map<string, FileTreeNode>();
52
61
  let rootLimitReached = false;
53
62
  let failedRootCount = 0;
@@ -88,9 +97,12 @@ export function useFileSearch({
88
97
  if (!cancelled) {
89
98
  if (roots.length > 0 && failedRootCount === roots.length) {
90
99
  setResult({
91
- status: "error",
92
- query,
93
- error: new Error("Could not search any root"),
100
+ requestIdentity,
101
+ state: {
102
+ status: "error",
103
+ query,
104
+ error: new Error("Could not search any root"),
105
+ },
94
106
  });
95
107
  return;
96
108
  }
@@ -110,33 +122,35 @@ export function useFileSearch({
110
122
  rank(left) - rank(right) || left.name.localeCompare(right.name),
111
123
  );
112
124
  setResult({
113
- status: "success",
114
- isPartial: failedRootCount > 0,
115
- query,
116
- files: sortedFiles.slice(0, RESULT_LIMIT),
117
- hasMore: rootLimitReached || sortedFiles.length > RESULT_LIMIT,
125
+ requestIdentity,
126
+ state: {
127
+ status: "success",
128
+ isPartial: failedRootCount > 0,
129
+ query,
130
+ files: sortedFiles.slice(0, RESULT_LIMIT),
131
+ hasMore: rootLimitReached || sortedFiles.length > RESULT_LIMIT,
132
+ },
118
133
  });
119
134
  }
120
135
  });
121
136
  pending.current = request.catch((error: unknown) => {
122
137
  if (!cancelled) {
123
- setResult({ status: "error", query, error });
138
+ setResult({
139
+ requestIdentity,
140
+ state: { status: "error", query, error },
141
+ });
124
142
  }
125
143
  });
126
144
  return () => {
127
145
  cancelled = true;
128
146
  };
129
- }, [query, debouncedQuery, tree, showHiddenFiles, revision, sendSearchFiles]);
147
+ }, [debouncedQuery, requestIdentity]);
130
148
 
131
149
  if (!query) {
132
150
  return { state: { status: "idle" }, refetch };
133
151
  }
134
- if (
135
- query !== debouncedQuery ||
136
- result.status === "idle" ||
137
- result.query !== query
138
- ) {
152
+ if (query !== debouncedQuery || result?.requestIdentity !== requestIdentity) {
139
153
  return { state: { status: "loading", query }, refetch };
140
154
  }
141
- return { state: result, refetch };
155
+ return { state: result.state, refetch };
142
156
  }
@@ -41,6 +41,7 @@ export const NotebookBanner: React.FC<Props> = ({ width }) => {
41
41
  {banner.title}
42
42
  </span>
43
43
  <Button
44
+ aria-label={`Dismiss ${banner.title}`}
44
45
  data-testid="remove-banner-button"
45
46
  variant="text"
46
47
  size="icon"
@@ -248,7 +248,7 @@ export const replaceTracebackFilenames = (domNode: DOMNode) => {
248
248
  <Tooltip content={tooltipContent}>
249
249
  <button
250
250
  type="button"
251
- className="ml-1 p-1 rounded-sm hover:bg-muted transition-all inline"
251
+ className="ml-1 p-1 rounded-sm hover:bg-muted transition-colors inline"
252
252
  >
253
253
  <BugPlayIcon
254
254
  onClick={() => {
@@ -274,7 +274,7 @@ export const replaceTracebackFilenames = (domNode: DOMNode) => {
274
274
  return (
275
275
  <div
276
276
  className="inline-block cursor-pointer text-destructive hover:underline"
277
- onClick={(_) => {
277
+ onClick={() => {
278
278
  getRequestClient().openFile({
279
279
  path: info.filePath,
280
280
  lineNumber: info.lineNumber,
@@ -358,6 +358,7 @@ export class AnsiReducer {
358
358
 
359
359
  export type StringOutputMessage = Omit<OutputMessage, "data"> & {
360
360
  data: string;
361
+ id?: number;
361
362
  };
362
363
 
363
364
  /**
@@ -369,6 +370,7 @@ export class StatefulOutputMessage implements OutputMessage {
369
370
  public readonly channel: OutputMessage["channel"];
370
371
  public readonly timestamp: OutputMessage["timestamp"];
371
372
  public readonly code: OutputMessage["code"];
373
+ public readonly id: StringOutputMessage["id"];
372
374
  private ansiReducer = new AnsiReducer();
373
375
  private _data: string;
374
376
 
@@ -379,39 +381,50 @@ export class StatefulOutputMessage implements OutputMessage {
379
381
  public static create(message: StringOutputMessage): StatefulOutputMessage {
380
382
  const ansiReducer = new AnsiReducer();
381
383
  ansiReducer.append(message.data);
382
- return new StatefulOutputMessage(
383
- message.mimetype,
384
- message.channel,
385
- message.timestamp,
384
+ return new StatefulOutputMessage({
385
+ mimetype: message.mimetype,
386
+ channel: message.channel,
387
+ timestamp: message.timestamp,
386
388
  ansiReducer,
387
- message.code,
388
- );
389
+ code: message.code,
390
+ id: message.id,
391
+ });
389
392
  }
390
393
 
391
- private constructor(
392
- mimetype: OutputMessage["mimetype"],
393
- channel: OutputMessage["channel"],
394
- timestamp: OutputMessage["timestamp"],
395
- ansiReducer: AnsiReducer,
396
- code: OutputMessage["code"],
397
- ) {
394
+ private constructor({
395
+ mimetype,
396
+ channel,
397
+ timestamp,
398
+ ansiReducer,
399
+ code,
400
+ id,
401
+ }: {
402
+ mimetype: OutputMessage["mimetype"];
403
+ channel: OutputMessage["channel"];
404
+ timestamp: OutputMessage["timestamp"];
405
+ ansiReducer: AnsiReducer;
406
+ code: OutputMessage["code"];
407
+ id: StringOutputMessage["id"];
408
+ }) {
398
409
  this.mimetype = mimetype;
399
410
  this.channel = channel;
400
411
  this.timestamp = timestamp;
401
412
  this.code = code;
413
+ this.id = id;
402
414
  this.ansiReducer = ansiReducer;
403
415
  this._data = this.ansiReducer.render();
404
416
  }
405
417
 
406
418
  public appendData(chunk: string): StatefulOutputMessage {
407
419
  this.ansiReducer.append(chunk);
408
- return new StatefulOutputMessage(
409
- this.mimetype,
410
- this.channel,
411
- this.timestamp,
412
- this.ansiReducer,
413
- this.code,
414
- );
420
+ return new StatefulOutputMessage({
421
+ mimetype: this.mimetype,
422
+ channel: this.channel,
423
+ timestamp: this.timestamp,
424
+ ansiReducer: this.ansiReducer,
425
+ code: this.code,
426
+ id: this.id,
427
+ });
415
428
  }
416
429
 
417
430
  public toJSON(): StringOutputMessage {
@@ -6,7 +6,7 @@ import {
6
6
  ChevronsUpDownIcon,
7
7
  WrapTextIcon,
8
8
  } from "lucide-react";
9
- import React, { useEffect, useLayoutEffect, useRef, useState } from "react";
9
+ import React, { useEffect, useLayoutEffect, useState } from "react";
10
10
  import { ToggleButton } from "react-aria-components";
11
11
  import { DebuggerControls } from "@/components/debugger/debugger-code";
12
12
  import { CopyClipboardIcon } from "@/components/icons/copy-icon";
@@ -16,8 +16,7 @@ import { Tooltip } from "@/components/ui/tooltip";
16
16
  import type { CellId } from "@/core/cells/ids";
17
17
  import { isInternalCellName } from "@/core/cells/names";
18
18
  import { useExpandedConsoleOutput } from "@/core/cells/outputs";
19
- import type { WithResponse } from "@/core/cells/types";
20
- import type { OutputMessage } from "@/core/kernel/messages";
19
+ import type { ConsoleOutputMessage } from "@/core/cells/types";
21
20
  import {
22
21
  type UseInputHistoryReturn,
23
22
  useInputHistory,
@@ -49,31 +48,21 @@ export const CONSOLE_CLEAR_DEBOUNCE_MS = 200;
49
48
  */
50
49
  function useDebouncedConsoleOutputs<T>(outputs: T[]): T[] {
51
50
  const [debouncedOutputs, setDebouncedOutputs] = useState(outputs);
52
- const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
53
51
 
54
- // Non-empty outputs: apply immediately and cancel any pending clear
52
+ // Non-empty outputs: apply immediately. Effect cleanup cancels pending clears.
55
53
  if (outputs.length > 0 && debouncedOutputs !== outputs) {
56
- if (timerRef.current !== null) {
57
- clearTimeout(timerRef.current);
58
- timerRef.current = null;
59
- }
60
54
  setDebouncedOutputs(outputs);
61
55
  }
62
56
 
63
57
  // Empty outputs: delay the clear so new outputs can arrive first
64
58
  useEffect(() => {
65
- if (outputs.length === 0 && timerRef.current === null) {
66
- timerRef.current = setTimeout(() => {
67
- timerRef.current = null;
68
- setDebouncedOutputs([]);
69
- }, CONSOLE_CLEAR_DEBOUNCE_MS);
59
+ if (outputs.length > 0) {
60
+ return;
70
61
  }
71
- return () => {
72
- if (timerRef.current !== null) {
73
- clearTimeout(timerRef.current);
74
- timerRef.current = null;
75
- }
76
- };
62
+ const timer = setTimeout(() => {
63
+ setDebouncedOutputs([]);
64
+ }, CONSOLE_CLEAR_DEBOUNCE_MS);
65
+ return () => clearTimeout(timer);
77
66
  }, [outputs]);
78
67
 
79
68
  return debouncedOutputs;
@@ -83,7 +72,7 @@ interface Props {
83
72
  cellId: CellId;
84
73
  cellName: string;
85
74
  className?: string;
86
- consoleOutputs: WithResponse<OutputMessage>[];
75
+ consoleOutputs: ConsoleOutputMessage[];
87
76
  stale: boolean;
88
77
  interrupted: boolean;
89
78
  debuggerActive: boolean;
@@ -251,19 +240,24 @@ const ConsoleOutputInternal = (props: Props): React.ReactNode => {
251
240
  return null;
252
241
  }
253
242
 
243
+ const originalIdx = consoleOutputs.length - idx - 1;
244
+ // Outputs render in reverse, so the reversed index shifts on every
245
+ // new line. The assigned id survives collapse and truncation. The
246
+ // original position is the fallback for outputs without one.
247
+ const key = output.id ?? `position-${originalIdx}`;
248
+
254
249
  if (output.channel === "stdin") {
255
250
  invariant(
256
251
  typeof output.data === "string",
257
252
  "Expected data to be a string",
258
253
  );
259
254
 
260
- const originalIdx = consoleOutputs.length - idx - 1;
261
255
  const isPassword = output.mimetype === "text/password";
262
256
 
263
257
  if (output.response == null && lastStdInputIdx === idx) {
264
258
  return (
265
259
  <StdInput
266
- key={idx}
260
+ key={key}
267
261
  output={output.data}
268
262
  isPdb={isPdb}
269
263
  isPassword={isPassword}
@@ -278,7 +272,7 @@ const ConsoleOutputInternal = (props: Props): React.ReactNode => {
278
272
 
279
273
  return (
280
274
  <StdInputWithResponse
281
- key={idx}
275
+ key={key}
282
276
  output={output.data}
283
277
  response={output.response}
284
278
  isPassword={isPassword}
@@ -288,7 +282,7 @@ const ConsoleOutputInternal = (props: Props): React.ReactNode => {
288
282
  }
289
283
 
290
284
  return (
291
- <React.Fragment key={idx}>
285
+ <React.Fragment key={key}>
292
286
  <OutputRenderer
293
287
  cellId={cellId}
294
288
  onRefactorWithAI={onRefactorWithAI}
@@ -158,6 +158,7 @@ export const PackageAlert: React.FC = () => {
158
158
  <SourceBadge source={packageAlert.source} />
159
159
  </span>
160
160
  <Button
161
+ aria-label="Dismiss missing packages alert"
161
162
  variant="text"
162
163
  data-testid="remove-banner-button"
163
164
  size="icon"
@@ -171,12 +172,12 @@ export const PackageAlert: React.FC = () => {
171
172
  <p>The following packages were not found:</p>
172
173
  <table className="list-disc ml-2 mt-1">
173
174
  <tbody>
174
- {packageAlert.packages.map((pkg, index) => {
175
+ {packageAlert.packages.map((pkg) => {
175
176
  const parsed = parsePackageSpecifier(pkg);
176
177
  const currentExtras = selectedExtras[pkg] || parsed.extras;
177
178
 
178
179
  return (
179
- <tr className="font-mono text-sm" key={index}>
180
+ <tr className="font-mono text-sm" key={pkg}>
180
181
  <td className="pr-2 py-1 align-middle">
181
182
  <BoxIcon size="1rem" />
182
183
  </td>
@@ -270,6 +271,7 @@ export const PackageAlert: React.FC = () => {
270
271
  <SourceBadge source={packageAlert.source} />
271
272
  </span>
272
273
  <Button
274
+ aria-label={`Dismiss ${title}`}
273
275
  variant="text"
274
276
  data-testid="remove-banner-button"
275
277
  size="icon"
@@ -294,7 +296,7 @@ export const PackageAlert: React.FC = () => {
294
296
  </>
295
297
  )}
296
298
  <ul className="list-disc ml-2 mt-1">
297
- {Object.entries(packageAlert.packages).map(([pkg, st], index) => (
299
+ {Object.entries(packageAlert.packages).map(([pkg, st]) => (
298
300
  <li
299
301
  className={cn(
300
302
  "flex items-center gap-1 font-mono text-sm",
@@ -305,7 +307,7 @@ export const PackageAlert: React.FC = () => {
305
307
  status === "failed" &&
306
308
  "text-muted-foreground",
307
309
  )}
308
- key={index}
310
+ key={pkg}
309
311
  >
310
312
  <ProgressIcon status={st} />
311
313
  {pkg}
@@ -190,12 +190,18 @@ export const GridLayoutRenderer: React.FC<Props> = ({
190
190
  cells: [...cellLayouts, dropped],
191
191
  });
192
192
  }}
193
- onDragStart={(_layout, _oldItem, _newItem, _placeholder, event) => {
194
- dragProps.onDragStart(event);
195
- }}
196
- onDrag={(_layout, _oldItem, _newItem, _placeholder, event) => {
197
- dragProps.onDragMove(event);
198
- }}
193
+ onDragStart={
194
+ // oxlint-disable-next-line marimo/prefer-object-params -- React Grid Layout supplies the event as the fifth argument.
195
+ (_layout, _oldItem, _newItem, _placeholder, event) => {
196
+ dragProps.onDragStart(event);
197
+ }
198
+ }
199
+ onDrag={
200
+ // oxlint-disable-next-line marimo/prefer-object-params -- React Grid Layout supplies the event as the fifth argument.
201
+ (_layout, _oldItem, _newItem, _placeholder, event) => {
202
+ dragProps.onDragMove(event);
203
+ }
204
+ }
199
205
  onDragStop={() => {
200
206
  dragProps.onDragStop();
201
207
  }}
@@ -12,7 +12,13 @@ interface SidebarToggleProps {
12
12
  export const SidebarToggle = ({ isOpen, toggle }: SidebarToggleProps) => {
13
13
  return (
14
14
  <div className="invisible lg:visible absolute top-[12px] right-[16px] z-20">
15
- <Button onClick={toggle} className="w-10 h-8" variant="ghost" size="icon">
15
+ <Button
16
+ aria-label={isOpen ? "Collapse sidebar" : "Expand sidebar"}
17
+ onClick={toggle}
18
+ className="w-10 h-8"
19
+ variant="ghost"
20
+ size="icon"
21
+ >
16
22
  <ChevronLeft
17
23
  className={cn(
18
24
  "h-5 w-5 transition-transform ease-in-out duration-700",
@@ -12,7 +12,6 @@ import {
12
12
  GraduationCapIcon,
13
13
  GridIcon,
14
14
  LayoutIcon,
15
- LinkIcon,
16
15
  MessagesSquareIcon,
17
16
  OrbitIcon,
18
17
  PackageIcon,
@@ -82,7 +81,12 @@ export const OpenTutorialDropDown: React.FC = () => {
82
81
  return (
83
82
  <DropdownMenu>
84
83
  <DropdownMenuTrigger asChild={true}>
85
- <Button data-testid="open-tutorial-button" size="xs" variant="outline">
84
+ <Button
85
+ data-testid="open-tutorial-button"
86
+ size="sm"
87
+ variant="outline"
88
+ className="border-border text-(--gray-11) shadow-none hover:border-(--gray-7) hover:bg-(--gray-3)"
89
+ >
86
90
  <GraduationCapIcon className="w-4 h-4 mr-2" />
87
91
  Tutorials
88
92
  <CaretDownIcon className="w-3 h-3 ml-1" />
@@ -135,7 +139,7 @@ const RESOURCES = [
135
139
  url: Constants.githubPage,
136
140
  },
137
141
  {
138
- title: "Community",
142
+ title: "Discussion",
139
143
  description: "Join the marimo Discord community",
140
144
  icon: MessagesSquareIcon,
141
145
  url: Constants.discordLink,
@@ -162,9 +166,11 @@ const RESOURCES = [
162
166
 
163
167
  export const ResourceLinks: React.FC = () => {
164
168
  return (
165
- <div className="flex flex-col gap-2">
166
- <Header Icon={LinkIcon}>Resources</Header>
167
- <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
169
+ <nav
170
+ aria-label="Resources"
171
+ className="mt-12 border-t border-border/60 pt-6"
172
+ >
173
+ <div className="flex flex-wrap items-center gap-x-6 gap-y-4">
168
174
  {RESOURCES.map((resource) => (
169
175
  <a
170
176
  key={resource.title}
@@ -172,19 +178,15 @@ export const ResourceLinks: React.FC = () => {
172
178
  target="_blank"
173
179
  rel="noreferrer"
174
180
  aria-label={`${resource.title}: ${resource.description}`}
175
- className="flex items-start gap-3 py-3 px-3 rounded-lg border hover:bg-accent/20 transition-colors shadow-xs"
181
+ title={resource.description}
182
+ className="flex items-center gap-2 rounded-sm text-sm text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
176
183
  >
177
- <resource.icon className="w-5 h-5 mt-1.5 text-primary" />
178
- <div>
179
- <h3 className="font-medium">{resource.title}</h3>
180
- <p className="text-sm text-muted-foreground">
181
- {resource.description}
182
- </p>
183
- </div>
184
+ <resource.icon className="h-4 w-4" />
185
+ <span>{resource.title}</span>
184
186
  </a>
185
187
  ))}
186
188
  </div>
187
- </div>
189
+ </nav>
188
190
  );
189
191
  };
190
192
 
@@ -203,19 +205,3 @@ export const TruncatedWorkspaceBanner: React.FC<{
203
205
  </Banner>
204
206
  );
205
207
  };
206
-
207
- export const Header: React.FC<{
208
- Icon: React.FC<React.SVGProps<SVGSVGElement>>;
209
- control?: React.ReactNode;
210
- children: React.ReactNode;
211
- }> = ({ Icon, control, children }) => {
212
- return (
213
- <div className="flex items-center justify-between gap-2">
214
- <h2 className="flex items-center gap-2 text-xl font-semibold text-muted-foreground select-none">
215
- <Icon className="h-5 w-5" />
216
- {children}
217
- </h2>
218
- {control}
219
- </div>
220
- );
221
- };
@@ -0,0 +1,39 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { act, render, screen } from "@testing-library/react";
4
+ import { Profiler } from "react";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
+ import { LargeSpinner } from "../large-spinner";
7
+
8
+ describe("LargeSpinner", () => {
9
+ beforeEach(() => vi.useFakeTimers());
10
+ afterEach(() => vi.useRealTimers());
11
+
12
+ it("fades the old title without a second commit before the timer fires", () => {
13
+ const onRender = vi.fn();
14
+ const spinner = (title: string) => (
15
+ <Profiler id="spinner" onRender={onRender}>
16
+ <LargeSpinner title={title} />
17
+ </Profiler>
18
+ );
19
+ const { rerender } = render(spinner("Starting"));
20
+ onRender.mockClear();
21
+
22
+ rerender(spinner("Loading"));
23
+ expect(onRender).toHaveBeenCalledOnce();
24
+ expect(screen.getByText("Starting")).toHaveClass("opacity-0");
25
+
26
+ act(() => vi.advanceTimersByTime(300));
27
+ expect(screen.getByText("Loading")).toHaveClass("opacity-100");
28
+ });
29
+
30
+ it("restores visibility if the title changes back during the fade", () => {
31
+ const { rerender } = render(<LargeSpinner title="Starting" />);
32
+ rerender(<LargeSpinner title="Loading" />);
33
+ rerender(<LargeSpinner title="Starting" />);
34
+
35
+ expect(screen.getByText("Starting")).toHaveClass("opacity-100");
36
+ act(() => vi.advanceTimersByTime(300));
37
+ expect(screen.queryByText("Loading")).not.toBeInTheDocument();
38
+ });
39
+ });
@@ -1,23 +1,12 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import { Loader2Icon } from "lucide-react";
4
- import { useEffect, useState } from "react";
4
+ import { useDebounce } from "@/hooks/useDebounce";
5
5
  import { cn } from "@/utils/cn";
6
6
 
7
7
  export const LargeSpinner = ({ title }: { title?: string }) => {
8
- const [currentTitle, setCurrentTitle] = useState(title);
9
- const [isVisible, setIsVisible] = useState(true);
10
-
11
- useEffect(() => {
12
- if (title !== currentTitle) {
13
- setIsVisible(false);
14
- const timer = setTimeout(() => {
15
- setCurrentTitle(title);
16
- setIsVisible(true);
17
- }, 300); // Wait for fade out animation to complete
18
- return () => clearTimeout(timer);
19
- }
20
- }, [title, currentTitle]);
8
+ const currentTitle = useDebounce(title, 300);
9
+ const isVisible = title === currentTitle;
21
10
 
22
11
  return (
23
12
  <div className="flex flex-col h-full flex-1 items-center justify-center p-4">
@@ -25,7 +25,7 @@ const spinnerVariants = cva("animate-spin", {
25
25
  const Spinner = React.forwardRef<
26
26
  SVGSVGElement,
27
27
  React.SVGProps<SVGSVGElement> & VariantProps<typeof spinnerVariants>
28
- >(({ className, children, centered, size, ...props }, ref) => (
28
+ >(({ className, children: _children, centered, size, ...props }, ref) => (
29
29
  <Loader2Icon
30
30
  ref={ref}
31
31
  className={cn(spinnerVariants({ centered, size }), className)}
@@ -41,7 +41,7 @@ export const MCPStatusIndicator: React.FC = () => {
41
41
  <Popover>
42
42
  <Tooltip content="MCP Status">
43
43
  <PopoverTrigger asChild={true}>
44
- <Button variant="text" size="icon">
44
+ <Button aria-label="MCP status" variant="text" size="icon">
45
45
  <PlugIcon
46
46
  className={cn(
47
47
  "h-4 w-4",