@marimo-team/frontend 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 (461) hide show
  1. package/dist/assets/CellStatus-fK4n2NPc.js +1 -0
  2. package/dist/assets/{ConnectedDataExplorerComponent-t8fH_TNc.js → ConnectedDataExplorerComponent-Dft8R6l5.js} +1 -1
  3. package/dist/assets/{ErrorBoundary-B4n9EnHB.js → ErrorBoundary-CAEGdoFW.js} +2 -2
  4. package/dist/assets/JsonOutput-CyAKmZDD.js +11 -0
  5. package/dist/assets/{LazyAnyLanguageCodeMirror-CKcCbG2G.js → LazyAnyLanguageCodeMirror-BPYE76jR.js} +2 -2
  6. package/dist/assets/MarimoErrorOutput-CgZvP0p0.js +10 -0
  7. package/dist/assets/{RenderHTML-9bMgbYmo.js → RenderHTML-DQvxDDNS.js} +1 -1
  8. package/dist/assets/RunButton-BBDnDL6G.js +1 -0
  9. package/dist/assets/__vite-browser-external-8vDMR0Kk.js +1 -0
  10. package/dist/assets/__vite-browser-external-mKcnhO20.js +1 -0
  11. package/dist/assets/{add-cell-with-ai-f_MnLCwI.js → add-cell-with-ai-Clg5FELx.js} +7 -7
  12. package/dist/assets/{add-connection-dialog-CoE_PhuD.js → add-connection-dialog-BqdWmD-7.js} +2 -2
  13. package/dist/assets/{agent-panel-BWZz24jK.js → agent-panel-5RxG5Y82.js} +3 -3
  14. package/dist/assets/ai-model-dropdown-Bvl1ktQq.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-BhU0ulYs.js → app-config-button-0QTeQ2Mv.js} +1 -1
  18. package/dist/assets/arrays-0R2reF2y.js +1 -0
  19. package/dist/assets/{blob-iOBwGaP9.js → blob-5VcB8K_t.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-Byw5y-oI.js +42 -0
  24. package/dist/assets/cell-link-C5yYyzuF.js +1 -0
  25. package/dist/assets/{cells-rA_jsZCj.js → cells-D_11MjVB.js} +60 -60
  26. package/dist/assets/{chat-display-BmEPmhDP.js → chat-display-BCR_ySiV.js} +1 -1
  27. package/dist/assets/chat-panel-RPRvBsaU.js +4 -0
  28. package/dist/assets/chat-ui-CGviVaRA.js +4 -0
  29. package/dist/assets/{circle-DXf-VWZG.js → circle-B6D_6D8w.js} +1 -1
  30. package/dist/assets/code-visibility-DYDagVy7.js +1 -0
  31. package/dist/assets/coerce-DWjFeXOY.js +1 -0
  32. package/dist/assets/column-preview-xe533-Cn.js +1 -0
  33. package/dist/assets/command-palette-BB9bWdWp.js +1 -0
  34. package/dist/assets/{common-lisZ0l6a.js → common-CaeItbcI.js} +1 -1
  35. package/dist/assets/components-CBE8cOLK.js +1 -0
  36. package/dist/assets/{components-CP_UoFtT.js → components-DCo7mAWO.js} +1 -1
  37. package/dist/assets/{components-Dvd1GnD6.js → components-G6frbw0c.js} +1 -1
  38. package/dist/assets/config-CD1IuZFJ.js +1 -0
  39. package/dist/assets/{config-DJLAC21v.js → config-DMz7oN0r.js} +1 -1
  40. package/dist/assets/config-DpY86_Yq.js +1 -0
  41. package/dist/assets/{copy-D8O_CYCG.js → copy-KlhazlhB.js} +1 -1
  42. package/dist/assets/{copy-icon-BIiRXh_3.js → copy-icon-8-a5vovc.js} +1 -1
  43. package/dist/assets/{createReducer-DgRODTW6.js → createReducer-CaI_Oa0v.js} +1 -1
  44. package/dist/assets/{datasource-Dr1xNKUn.js → datasource-deuXnlU-.js} +1 -1
  45. package/dist/assets/{dates-CfmdTL6B.js → dates-CQMwFko2.js} +1 -1
  46. package/dist/assets/{dependency-graph-panel-B0uP-_id.js → dependency-graph-panel-CIykLnJe.js} +2 -2
  47. package/dist/assets/{dist-iaThVDkA.js → dist-jlhMTKo_.js} +1 -1
  48. package/dist/assets/{documentation-panel-DVl_ebWy.js → documentation-panel-2PQ8kQLa.js} +1 -1
  49. package/dist/assets/{download-Bx55LMki.js → download-dK52bLxZ.js} +1 -1
  50. package/dist/assets/{dropdown-menu-C1rnl-0o.js → dropdown-menu-DS6umAnH.js} +1 -1
  51. package/dist/assets/edit-page-DrElzw10.js +9 -0
  52. package/dist/assets/{error-banner-Cd69vvyX.js → error-banner-B5kzVf_0.js} +1 -1
  53. package/dist/assets/{error-entries-DOWpdGvI.js → error-entries-3GVk2KjS.js} +1 -1
  54. package/dist/assets/{error-panel-DpbNKSLs.js → error-panel-CzDRGPeC.js} +1 -1
  55. package/dist/assets/{errors-DCrsa1nH.js → errors-CNsgAw6m.js} +1 -1
  56. package/dist/assets/{es-Cqst59WX.js → es-N9SHpmG7.js} +1 -1
  57. package/dist/assets/{feedback-button-B99ThNJW.js → feedback-button-C5NgWpFg.js} +2 -2
  58. package/dist/assets/feedback-button-CONwLFpw.js +1 -0
  59. package/dist/assets/file-explorer-panel-B50xDnTZ.js +1 -0
  60. package/dist/assets/{file-name-input-ChnBsDE_.js → file-name-input-DqwMSgGU.js} +1 -1
  61. package/dist/assets/{floating-outline-MYqQW4Vp.js → floating-outline-ChmAuvxz.js} +1 -1
  62. package/dist/assets/{formats-yZ06h1Pl.js → formats-zylfwejq.js} +1 -1
  63. package/dist/assets/{formatting-BqrP-n49.js → formatting-uom1es4b.js} +1 -1
  64. package/dist/assets/{fullscreen-B9tHcfpV.js → fullscreen-BWTs8pYZ.js} +1 -1
  65. package/dist/assets/{gallery-page-DSL0J4md.js → gallery-page-CiL8aMS1.js} +1 -1
  66. package/dist/assets/{ganttDiagram-EL5Y4UJY-2n7Ig5Jm.js → ganttDiagram-EL5Y4UJY-CYqkCfFG.js} +1 -1
  67. package/dist/assets/{glide-data-editor-B1MWlKeM.js → glide-data-editor-CqRclBkp.js} +2 -2
  68. package/dist/assets/{glide-utils-CIrAxdjv.js → glide-utils-HFqhvWHI.js} +2 -2
  69. package/dist/assets/home-page-BgZVl4dP.js +1 -0
  70. package/dist/assets/{hooks-BIogTGCr.js → hooks-DM5n7Osw.js} +1 -1
  71. package/dist/assets/{html-to-image-DK9Zl1lU.js → html-to-image-D_0rd3_B.js} +1 -1
  72. package/dist/assets/index-BnhSnwvB.css +2 -0
  73. package/dist/assets/index-EG2n8Oht.js +30 -0
  74. package/dist/assets/{input-BoBxR50a.js → input-DmHMEZGh.js} +1 -1
  75. package/dist/assets/kiosk-mode-DFS6HopF.js +1 -0
  76. package/dist/assets/layout-BpP8nTjb.js +9 -0
  77. package/dist/assets/linear-Bma8J3iM.js +1 -0
  78. package/dist/assets/links-DX2w97N9.js +1 -0
  79. package/dist/assets/{logs-panel-y0XnfulJ.js → logs-panel-B5eVAZtH.js} +1 -1
  80. package/dist/assets/{markdown-renderer-Q5AhbRU3.js → markdown-renderer-BWUzqkJM.js} +1 -1
  81. package/dist/assets/{menu-items-BrwnTgyK.js → menu-items-BgUyNUpu.js} +1 -1
  82. package/dist/assets/{mermaid-a3xYymTy.js → mermaid-BKXlWk4F.js} +2 -2
  83. package/dist/assets/{name-cell-input-BNOnF6-0.js → name-cell-input-DXW68gRW.js} +1 -1
  84. package/dist/assets/numbers-CsZVw3Dc.js +1 -0
  85. package/dist/assets/{objects-snlMn4yk.js → objects-rLPlQ3X_.js} +1 -1
  86. package/dist/assets/{once-Haz_uxEL.js → once-6KNTRW0u.js} +1 -1
  87. package/dist/assets/{outline-panel-Be7stWwv.js → outline-panel-DPxJhJ_A.js} +1 -1
  88. package/dist/assets/{package-data-dv4FBHxR.js → package-data-CKPmBDtg.js} +1 -1
  89. package/dist/assets/packages-panel-gBE2FWQr.js +1 -0
  90. package/dist/assets/{panel-accordion-state-GeMEdCPw.js → panel-accordion-state-CCF5Gxnc.js} +1 -1
  91. package/dist/assets/panels-Dr6qMCvU.js +105 -0
  92. package/dist/assets/precisionRound-BxlUs8G4.js +1 -0
  93. package/dist/assets/process-output-Cw9IDcKQ.js +1 -0
  94. package/dist/assets/{quadrantDiagram-AXDQQJYC-yBhCDI7E.js → quadrantDiagram-AXDQQJYC-D9KH5Zge.js} +1 -1
  95. package/dist/assets/react-vega-BTNeboR2.js +189 -0
  96. package/dist/assets/{readonly-code-display-YULOXf4h.js → readonly-code-display-DYcZQ28T.js} +1 -1
  97. package/dist/assets/{readonly-python-code-CadgXgRu.js → readonly-python-code-RFuQP50v.js} +1 -1
  98. package/dist/assets/{renderShortcut-BkVJQ_Zh.js → renderShortcut-rUwGqKgC.js} +1 -1
  99. package/dist/assets/{requests-lt0JK8Jt.js → requests-BIFR-h4M.js} +1 -1
  100. package/dist/assets/requests-DuLJN9Tf.js +1 -0
  101. package/dist/assets/{reveal-component-BVAR7ayZ.js → reveal-component-yRrDmCQz.js} +37 -37
  102. package/dist/assets/run-page-B69M4-zE.js +1 -0
  103. package/dist/assets/{runs-BLp5jezv.js → runs-C49hleBy.js} +1 -1
  104. package/dist/assets/{runtime-details-state-DuChzsA6.js → runtime-details-state-pobtu6TO.js} +1 -1
  105. package/dist/assets/{save-worker-BbUSwZRG.js → save-worker-pCkSHhuz.js} +1 -1
  106. package/dist/assets/{schemas-Dv42wLFq.js → schemas-ChRqMOTL.js} +1 -1
  107. package/dist/assets/scratchpad-panel-H44_pvb_.js +1 -0
  108. package/dist/assets/{scroll-to-bottom-button-1FyowpWV.js → scroll-to-bottom-button-CIkON2WJ.js} +1 -1
  109. package/dist/assets/{secrets-panel-Bctbtkrp.js → secrets-panel-bcfEKxRx.js} +1 -1
  110. package/dist/assets/{select-DTBn5eTb.js → select-3CVGlYrC.js} +1 -1
  111. package/dist/assets/{session-DqoWmb7X.js → session-CMLIzZKw.js} +1 -1
  112. package/dist/assets/session-panel-Bnd1iXkO.js +1 -0
  113. package/dist/assets/{snippets-panel-CQ1dn3lA.js → snippets-panel-DKApxKx9.js} +1 -1
  114. package/dist/assets/{spec-BfSBcO5B.js → spec-uCpVvaAR.js} +1 -1
  115. package/dist/assets/{state-uwRvJLb8.js → state-3mcyTS9q.js} +1 -1
  116. package/dist/assets/state-B_MCKzBL.js +1 -0
  117. package/dist/assets/state-Bl9BiY_s.js +3 -0
  118. package/dist/assets/{state-C3oDoyFe.js → state-C5WAeuTu.js} +1 -1
  119. package/dist/assets/{state-DxQnC_1I.js → state-Cv2JW3l9.js} +1 -1
  120. package/dist/assets/{state-Bfh_2SLl.js → state-Dv059B3A.js} +1 -1
  121. package/dist/assets/{strings-B2vv-ENv.js → strings-C25fhMR_.js} +1 -1
  122. package/dist/assets/{swiper-component-a07domrZ.js → swiper-component-CRGRy5_O.js} +1 -1
  123. package/dist/assets/{switch-CTaiAjrz.js → switch-DYjBfpNJ.js} +1 -1
  124. package/dist/assets/{tabs-BuQlZFbt.js → tabs-CY8E4r9U.js} +1 -1
  125. package/dist/assets/{terminal-Ck_vK-34.js → terminal-Ca-kubKb.js} +1 -1
  126. package/dist/assets/{time-MgToa8VM.js → time-DswSXBDk.js} +1 -1
  127. package/dist/assets/{toast-BGzLGnvw.js → toast-Gzxi-le_.js} +1 -1
  128. package/dist/assets/tooltip-Brya4Ghn.js +61 -0
  129. package/dist/assets/{tooltip-C9TuY18o.js → tooltip-CkGm8X7m.js} +1 -1
  130. package/dist/assets/tracing-Clwy4SlW.js +1 -0
  131. package/dist/assets/tracing-panel-BwjLG_aC.js +2 -0
  132. package/dist/assets/{tree-actions-DAd80MbP.js → tree-actions-BZNTQ12N.js} +1 -1
  133. package/dist/assets/{use-restore-focus-BxEGwIbk.js → use-restore-focus-DdS7Tl0F.js} +1 -1
  134. package/dist/assets/{useAddCell-DGstJK0S.js → useAddCell-Cjuqk2YR.js} +1 -1
  135. package/dist/assets/useAsyncData-qvoPYHtx.js +1 -0
  136. package/dist/assets/useBoolean-Dd2nWXFJ.js +1 -0
  137. package/dist/assets/useCellActionButton-MoWLvuGe.js +1 -0
  138. package/dist/assets/useDebounce-DwTQ_1Pu.js +1 -0
  139. package/dist/assets/{useDeepCompareMemoize-DFIVN9dn.js → useDeepCompareMemoize-B2DVK2De.js} +1 -1
  140. package/dist/assets/useDelayElapsed-BlsDT4TV.js +1 -0
  141. package/dist/assets/{useDeleteCell-DFBGlSQk.js → useDeleteCell-BC-MW7d1.js} +1 -1
  142. package/dist/assets/{useDependencyPanelTab-DydxsE5d.js → useDependencyPanelTab-CZU7i-B3.js} +1 -1
  143. package/dist/assets/{useEvent-Dfcbgjhg.js → useEvent-BDjgaBtz.js} +1 -1
  144. package/dist/assets/{useEventListener-D9cftcHj.js → useEventListener-CjDeLqFC.js} +1 -1
  145. package/dist/assets/{useIframeCapabilities-CwTkczVN.js → useIframeCapabilities-Czr9e4uG.js} +1 -1
  146. package/dist/assets/{useInstallPackage-iX1eufdl.js → useInstallPackage-wcDq9hYT.js} +1 -1
  147. package/dist/assets/{useInterval--x3s1VpI.js → useInterval-DJdQmXnq.js} +1 -1
  148. package/dist/assets/useNotebookActions-wFxVdRXQ.js +1 -0
  149. package/dist/assets/{useRunCells-BFjFX3hz.js → useRunCells-ByaWxoIt.js} +1 -1
  150. package/dist/assets/{useSplitCell-Tqv5pPtL.js → useSplitCell-YJIystwv.js} +1 -1
  151. package/dist/assets/useStateForKey-CGMJQT8x.js +1 -0
  152. package/dist/assets/{useTheme-COhSHkCj.js → useTheme-BAA27lh7.js} +1 -1
  153. package/dist/assets/{utils-CaQYvazl.js → utils-DfElfBpz.js} +1 -1
  154. package/dist/assets/vega-component-BeyxAJad.js +1 -0
  155. package/dist/assets/{vega-embed-container-DJPNkHPb.js → vega-embed-container-DtArSJz1.js} +1 -1
  156. package/dist/assets/vega-loader.browser-Bu25TKax.js +6 -0
  157. package/dist/assets/{worker-DTQ7oH2A.js → worker-bSCjCvwz.js} +1 -1
  158. package/dist/assets/{write-secret-modal-DzKAInL2.js → write-secret-modal-BEx-EUlh.js} +1 -1
  159. package/dist/assets/{xychartDiagram-S5SC5T6Z-KbGzG6-u.js → xychartDiagram-S5SC5T6Z-CeRnBS5e.js} +1 -1
  160. package/dist/index.html +80 -78
  161. package/package.json +7 -7
  162. package/src/__tests__/host-imports.test.ts +1 -0
  163. package/src/components/app-config/ai-config.tsx +7 -2
  164. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  165. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  166. package/src/components/chat/acp/blocks.tsx +2 -2
  167. package/src/components/chat/chat-components.tsx +2 -0
  168. package/src/components/chat/chat-panel.tsx +9 -8
  169. package/src/components/chat/chat-utils.ts +4 -8
  170. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  171. package/src/components/data-table/__tests__/columns.test.tsx +105 -0
  172. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  173. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  174. package/src/components/data-table/__tests__/export-actions.test.tsx +105 -0
  175. package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
  176. package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
  177. package/src/components/data-table/cell-selection/feature.ts +2 -2
  178. package/src/components/data-table/cell-selection/types.ts +2 -2
  179. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
  180. package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
  181. package/src/components/data-table/charts/charts.tsx +35 -25
  182. package/src/components/data-table/charts/storage.ts +16 -5
  183. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  184. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  185. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  186. package/src/components/data-table/columns.tsx +45 -46
  187. package/src/components/data-table/context-menu.tsx +36 -20
  188. package/src/components/data-table/data-table.tsx +7 -1
  189. package/src/components/data-table/date-popover.tsx +6 -4
  190. package/src/components/data-table/export-actions.tsx +76 -48
  191. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +2 -2
  192. package/src/components/data-table/search-highlight.tsx +79 -0
  193. package/src/components/data-table/types.ts +1 -0
  194. package/src/components/data-table/url-detector.tsx +49 -9
  195. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  196. package/src/components/datasources/__tests__/column-preview.test.tsx +63 -2
  197. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  198. package/src/components/datasources/column-preview.tsx +8 -2
  199. package/src/components/datasources/datasources.tsx +47 -35
  200. package/src/components/debugger/debugger-code.tsx +5 -0
  201. package/src/components/dependency-graph/panels.tsx +2 -1
  202. package/src/components/editor/SortableCell.tsx +1 -3
  203. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  204. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +24 -0
  205. package/src/components/editor/actions/pair-with-agent-modal.tsx +18 -15
  206. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  207. package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
  208. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  209. package/src/components/editor/cell/CreateCellButton.tsx +8 -10
  210. package/src/components/editor/cell/DeleteButton.tsx +1 -0
  211. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  212. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  213. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  214. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  215. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  216. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
  217. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  218. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  219. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +12 -18
  220. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +58 -1
  221. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  222. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
  223. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  224. package/src/components/editor/connections/components.tsx +1 -1
  225. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  226. package/src/components/editor/controls/command-palette.tsx +22 -10
  227. package/src/components/editor/dynamic-favicon.tsx +21 -40
  228. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +12 -23
  229. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  230. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  231. package/src/components/editor/file-tree/file-explorer.tsx +10 -5
  232. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  233. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  234. package/src/components/editor/notebook-banner.tsx +1 -0
  235. package/src/components/editor/output/MarimoTracebackOutput.tsx +2 -2
  236. package/src/components/editor/output/ansi-reduce.ts +6 -0
  237. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  238. package/src/components/editor/package-alert.tsx +6 -4
  239. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  240. package/src/components/home/components.tsx +17 -31
  241. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  242. package/src/components/icons/large-spinner.tsx +3 -14
  243. package/src/components/icons/spinner.tsx +1 -1
  244. package/src/components/mcp/mcp-status-indicator.tsx +1 -1
  245. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  246. package/src/components/pages/home-page.tsx +164 -91
  247. package/src/components/scratchpad/scratchpad.tsx +2 -2
  248. package/src/components/slides/reveal-component.tsx +4 -2
  249. package/src/components/slides/slide-cell-view.tsx +1 -1
  250. package/src/components/slides/slide-form.tsx +1 -0
  251. package/src/components/tracing/tracing.tsx +2 -2
  252. package/src/components/ui/alert-dialog.tsx +1 -1
  253. package/src/core/ai/context/providers/datasource.ts +6 -2
  254. package/src/core/attachments/state.ts +5 -8
  255. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -0
  256. package/src/core/cells/__tests__/cell.test.ts +39 -0
  257. package/src/core/cells/__tests__/cells.test.ts +3 -1
  258. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
  259. package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
  260. package/src/core/cells/cell.ts +14 -3
  261. package/src/core/cells/cells.ts +3 -1
  262. package/src/core/cells/pending-delete-service.ts +2 -6
  263. package/src/core/cells/session.ts +1 -1
  264. package/src/core/cells/types.ts +10 -1
  265. package/src/core/codemirror/__tests__/setup.test.ts +1 -1
  266. package/src/core/codemirror/keymaps/vim.ts +2 -6
  267. package/src/core/codemirror/language/languages/markdown.ts +1 -1
  268. package/src/core/codemirror/language/languages/python.ts +3 -3
  269. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  270. package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
  271. package/src/core/datasets/data-source-discovery.ts +4 -6
  272. package/src/core/document/__tests__/draft.test.ts +322 -0
  273. package/src/core/document/__tests__/fixtures.ts +92 -0
  274. package/src/core/document/__tests__/lifecycle-scenarios.test.ts +61 -0
  275. package/src/core/document/__tests__/model.test.ts +158 -0
  276. package/src/core/document/__tests__/protocol-fixtures.ts +38 -0
  277. package/src/core/document/__tests__/recovery-scenarios.test.ts +230 -0
  278. package/src/core/document/__tests__/scenario-fixture.ts +42 -0
  279. package/src/core/document/__tests__/scenarios.test.ts +205 -0
  280. package/src/core/document/__tests__/state.test.ts +446 -0
  281. package/src/core/document/__tests__/submission.test.ts +201 -0
  282. package/src/core/document/__tests__/test-host.test.ts +92 -0
  283. package/src/core/document/__tests__/test-host.ts +247 -0
  284. package/src/core/document/__tests__/test-notebook.ts +139 -0
  285. package/src/core/document/__tests__/user.ts +61 -0
  286. package/src/core/document/draft.ts +472 -0
  287. package/src/core/document/index.ts +10 -0
  288. package/src/core/document/model.ts +53 -0
  289. package/src/core/document/state.ts +401 -0
  290. package/src/core/document/sync.ts +359 -0
  291. package/src/core/edit-app.tsx +2 -2
  292. package/src/core/host/AGENTS.md +12 -2
  293. package/src/core/host/__tests__/attachments.test.ts +56 -0
  294. package/src/core/host/__tests__/document.test.ts +126 -0
  295. package/src/core/host/__tests__/fixtures.ts +41 -0
  296. package/src/core/host/__tests__/notebook-client.test.ts +570 -0
  297. package/src/core/host/connection.ts +42 -0
  298. package/src/core/host/notebook-client.ts +297 -0
  299. package/src/core/host/resources/attachments.ts +22 -0
  300. package/src/core/host/resources/document.ts +101 -0
  301. package/src/core/hotkeys/actions.ts +2 -4
  302. package/src/core/islands/__tests__/bootstrap.test.ts +0 -26
  303. package/src/core/islands/bootstrap.ts +0 -1
  304. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  305. package/src/core/network/connection.ts +4 -2
  306. package/src/core/network/useConnectionNotice.ts +8 -2
  307. package/src/core/run-app.tsx +2 -2
  308. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  309. package/src/core/runtime/__tests__/runtime.test.ts +18 -17
  310. package/src/core/runtime/runtime.ts +26 -30
  311. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  312. package/src/core/state/jotai.ts +1 -1
  313. package/src/core/static/files.ts +2 -0
  314. package/src/core/wasm/bridge.ts +2 -10
  315. package/src/core/wasm/store.ts +3 -3
  316. package/src/core/wasm/worker/bootstrap.ts +1 -1
  317. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  318. package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -29
  319. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +223 -0
  320. package/src/core/websocket/__tests__/useNotebookSession.hook.test.tsx +606 -0
  321. package/src/core/websocket/connection-state.ts +60 -0
  322. package/src/core/websocket/state.ts +7 -0
  323. package/src/core/websocket/transports/__tests__/notebook.test.ts +502 -0
  324. package/src/core/websocket/transports/notebook.ts +213 -0
  325. package/src/core/websocket/types.ts +9 -39
  326. package/src/core/websocket/useNotebookClient.ts +89 -0
  327. package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +53 -233
  328. package/src/css/globals.css +5 -0
  329. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  330. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  331. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  332. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  333. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  334. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  335. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  336. package/src/hooks/useAsyncData.ts +3 -3
  337. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  338. package/src/hooks/useDebounce.ts +5 -8
  339. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  340. package/src/hooks/useDelayElapsed.ts +14 -10
  341. package/src/hooks/useElapsedTime.ts +3 -3
  342. package/src/hooks/useInputHistory.ts +4 -7
  343. package/src/hooks/useInternalStateWithSync.ts +6 -6
  344. package/src/hooks/useResizeObserver.ts +4 -7
  345. package/src/hooks/useScript.ts +67 -70
  346. package/src/hooks/useStateForKey.ts +93 -0
  347. package/src/plugins/core/registerReactComponent.tsx +1 -1
  348. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  349. package/src/plugins/impl/DataTablePlugin.tsx +14 -15
  350. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  351. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  352. package/src/plugins/impl/FormPlugin.tsx +1 -1
  353. package/src/plugins/impl/RangeSliderPlugin.tsx +0 -1
  354. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  355. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  356. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +117 -1
  357. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  358. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  359. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
  360. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  361. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  362. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  363. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  364. package/src/plugins/impl/chat/chat-ui.tsx +12 -10
  365. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  366. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  367. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  368. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  369. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  370. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +0 -1
  371. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  372. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  373. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  374. package/src/plugins/impl/panel/utils.ts +17 -0
  375. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  376. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  377. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  378. package/src/stories/button.stories.tsx +1 -0
  379. package/src/stories/editor.stories.tsx +24 -17
  380. package/src/stories/form-wrapper.stories.tsx +2 -2
  381. package/src/stories/select.stories.tsx +2 -2
  382. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  383. package/src/utils/__tests__/arrays.test.ts +35 -0
  384. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  385. package/src/utils/__tests__/numbers.test.ts +48 -1
  386. package/src/utils/__tests__/objects.test.ts +21 -0
  387. package/src/utils/arrays.ts +10 -0
  388. package/src/utils/blob.ts +1 -1
  389. package/src/utils/numbers.ts +28 -17
  390. package/src/utils/objects.ts +3 -7
  391. package/src/utils/once.ts +19 -19
  392. package/src/utils/zod-utils.ts +1 -3
  393. package/dist/assets/CellStatus-nZpuDkm7.js +0 -1
  394. package/dist/assets/JsonOutput-BxB0NL8w.js +0 -11
  395. package/dist/assets/MarimoErrorOutput-BTy4MOzX.js +0 -10
  396. package/dist/assets/RunButton-CFQUNN5c.js +0 -1
  397. package/dist/assets/__vite-browser-external-CYO_P3S8.js +0 -1
  398. package/dist/assets/__vite-browser-external-FeDVGZU8.js +0 -1
  399. package/dist/assets/ai-model-dropdown-zm-2fXzf.js +0 -13
  400. package/dist/assets/any-language-editor-BLCAD3yx.js +0 -1
  401. package/dist/assets/arrays-BqUx5USx.js +0 -1
  402. package/dist/assets/cell-editor-CAj1UKxx.js +0 -21
  403. package/dist/assets/cell-link-1CynTVGQ.js +0 -1
  404. package/dist/assets/chat-panel-Ce2Xh7dM.js +0 -4
  405. package/dist/assets/chat-ui-DTy98rP3.js +0 -4
  406. package/dist/assets/circle-play-CYIb0qT6.js +0 -1
  407. package/dist/assets/code-visibility-B-_GvwR5.js +0 -1
  408. package/dist/assets/coerce-CpE6kUdX.js +0 -1
  409. package/dist/assets/column-preview-DU6dT80y.js +0 -1
  410. package/dist/assets/command-palette-BMqFKXVr.js +0 -1
  411. package/dist/assets/components-CXfO-F47.js +0 -1
  412. package/dist/assets/config-DX8Y2WbJ.js +0 -1
  413. package/dist/assets/config-VPSq73n8.js +0 -1
  414. package/dist/assets/edit-page-DlfCLUaq.js +0 -9
  415. package/dist/assets/feedback-button-DlqZkJwz.js +0 -1
  416. package/dist/assets/file-explorer-panel-Bo9eN3rW.js +0 -1
  417. package/dist/assets/home-page-KrhQ55Vg.js +0 -4
  418. package/dist/assets/index-6b4mf8Ai.css +0 -2
  419. package/dist/assets/index-B3fcrmuf.js +0 -30
  420. package/dist/assets/kiosk-mode-BZECfg9k.js +0 -1
  421. package/dist/assets/layout-DOpdUwym.js +0 -9
  422. package/dist/assets/linear-0eij_aAG.js +0 -1
  423. package/dist/assets/link-NuFVR7G9.js +0 -1
  424. package/dist/assets/links-DkkL9DVa.js +0 -1
  425. package/dist/assets/numbers-Mk3-CcoU.js +0 -1
  426. package/dist/assets/packages-panel-ClxOAEGy.js +0 -1
  427. package/dist/assets/panels-DfCmzLtT.js +0 -4
  428. package/dist/assets/precisionRound-Bq_nmf5u.js +0 -1
  429. package/dist/assets/process-output-DNFKMODr.js +0 -1
  430. package/dist/assets/react-vega-C3rYGtP6.js +0 -189
  431. package/dist/assets/requests-CgSY1X4a.js +0 -1
  432. package/dist/assets/run-page-Cxu1yV8Q.js +0 -1
  433. package/dist/assets/scratchpad-panel-By-ybhjg.js +0 -1
  434. package/dist/assets/session-panel-BmW6PH4z.js +0 -1
  435. package/dist/assets/state-BAJYRhmO.js +0 -1
  436. package/dist/assets/state-z3dlofZB.js +0 -3
  437. package/dist/assets/tooltip-mT3CGest.js +0 -61
  438. package/dist/assets/tracing-BARlJ_Dk.js +0 -1
  439. package/dist/assets/tracing-panel-dgnr2fIN.js +0 -2
  440. package/dist/assets/useAsyncData-C7aJJz0Y.js +0 -1
  441. package/dist/assets/useBoolean-BDD6_xQQ.js +0 -1
  442. package/dist/assets/useCellActionButton-DY9RlJTb.js +0 -1
  443. package/dist/assets/useDebounce-B_ccBZTp.js +0 -1
  444. package/dist/assets/useNotebookActions-DfvB7RD_.js +0 -1
  445. package/dist/assets/vega-component-CnadTgoE.js +0 -1
  446. package/dist/assets/vega-loader.browser-CLRkNgm7.js +0 -6
  447. package/dist/assets/ws-BXWY541V.js +0 -22
  448. package/src/core/host/__tests__/protocol.test.ts +0 -214
  449. package/src/core/host/protocol.ts +0 -1943
  450. package/src/core/state/__tests__/jotai.test.ts +0 -25
  451. package/src/core/websocket/__tests__/useMarimoKernelConnection.hook.test.tsx +0 -789
  452. package/src/core/websocket/__tests__/useWebSocket.test.ts +0 -49
  453. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  454. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -338
  455. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  456. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  457. package/src/core/websocket/transports/basic.ts +0 -102
  458. package/src/core/websocket/transports/sse.ts +0 -308
  459. package/src/core/websocket/transports/transport.ts +0 -61
  460. package/src/core/websocket/transports/ws.ts +0 -98
  461. package/src/core/websocket/useWebSocket.tsx +0 -88
@@ -12,7 +12,7 @@ import { Logger } from "@/utils/Logger";
12
12
  import type { Cell } from "@tanstack/react-table";
13
13
  import { useAtomValue } from "jotai";
14
14
  import { CopyIcon, FilterIcon, SquareStack } from "lucide-react";
15
- import type { RefObject } from "react";
15
+ import { type RefObject, useRef, useState } from "react";
16
16
  import useEvent from "react-use-event-hook";
17
17
  import {
18
18
  ContextMenu,
@@ -123,8 +123,21 @@ export const DataTableContextMenu = <TData,>({
123
123
  tableRef: RefObject<HTMLTableSectionElement | null>;
124
124
  copyAllCells: () => void;
125
125
  }) => {
126
+ const [activeCell, setActiveCell] = useState<Cell<TData, unknown> | null>(
127
+ null,
128
+ );
129
+ const highlightedCell = useRef<Element | null>(null);
126
130
  const handleContextMenuChange = useEvent((open: boolean) => {
131
+ highlightedCell.current?.classList.remove("bg-(--green-4)");
132
+ highlightedCell.current = null;
133
+ if (!open) {
134
+ // Forget the cell so a later right-click outside any cell can't reuse it
135
+ contextMenuRef.current = null;
136
+ return;
137
+ }
138
+
127
139
  const cell = contextMenuRef.current;
140
+ setActiveCell(cell);
128
141
  if (!cell) {
129
142
  return;
130
143
  }
@@ -138,42 +151,45 @@ export const DataTableContextMenu = <TData,>({
138
151
  return;
139
152
  }
140
153
 
141
- if (open) {
142
- cellElement.classList.add("bg-(--green-4)");
143
- } else {
144
- cellElement.classList.remove("bg-(--green-4)");
145
- }
154
+ cellElement.classList.add("bg-(--green-4)");
155
+ highlightedCell.current = cellElement;
146
156
  });
147
157
 
148
158
  return (
149
- <ContextMenu onOpenChange={handleContextMenuChange}>
150
- <ContextMenuTrigger asChild={true}>{tableBody}</ContextMenuTrigger>
159
+ // Open via the trigger so a right-click while already open retargets the
160
+ // menu; Radix only reports open changes, so use it for closing.
161
+ <ContextMenu
162
+ onOpenChange={(open) => {
163
+ if (!open) {
164
+ handleContextMenuChange(false);
165
+ }
166
+ }}
167
+ >
168
+ <ContextMenuTrigger
169
+ asChild={true}
170
+ onContextMenu={() => handleContextMenuChange(true)}
171
+ >
172
+ {tableBody}
173
+ </ContextMenuTrigger>
151
174
  <ContextMenuPortal>
152
- <CellContextMenu
153
- cellRef={contextMenuRef}
154
- copySelectedCells={copyAllCells}
155
- />
175
+ {activeCell && (
176
+ <CellContextMenu cell={activeCell} copySelectedCells={copyAllCells} />
177
+ )}
156
178
  </ContextMenuPortal>
157
179
  </ContextMenu>
158
180
  );
159
181
  };
160
182
 
161
183
  export const CellContextMenu = <TData,>({
162
- cellRef,
184
+ cell,
163
185
  copySelectedCells,
164
186
  }: {
165
- cellRef: RefObject<Cell<TData, unknown> | null>;
187
+ cell: Cell<TData, unknown>;
166
188
  copySelectedCells: () => void;
167
189
  }) => {
168
190
  const selectedCells = useAtomValue(selectedCellsAtom);
169
191
  const multipleSelectedCells = selectedCells.size > 1;
170
192
 
171
- const cell = cellRef.current;
172
- if (!cell) {
173
- Logger.error("No cell found in context menu");
174
- return;
175
- }
176
-
177
193
  const table = cell.getContext().table;
178
194
  const displayedValue = cell.getValue();
179
195
  const rawValue =
@@ -58,6 +58,7 @@ import { useColumnPinning } from "./hooks/use-column-pinning";
58
58
  import { useScrollContainerHeight } from "./hooks/use-scroll-container-height";
59
59
  import { CellSelectionProvider } from "./range-focus/provider";
60
60
  import { DataTableBody, renderTableHeader } from "./renderers";
61
+ import { buildHighlightPattern } from "./search-highlight";
61
62
  import { TableBottomBar } from "./TableBottomBar";
62
63
  import { TableTopBar } from "./TableTopBar";
63
64
  import {
@@ -231,6 +232,11 @@ const DataTableInternal = <TData,>({
231
232
  return idx + offset;
232
233
  }
233
234
 
235
+ const highlightPattern = React.useMemo(
236
+ () => buildHighlightPattern(searchQuery),
237
+ [searchQuery],
238
+ );
239
+
234
240
  const table = useReactTable<TData>({
235
241
  _features: [
236
242
  ColumnPinning,
@@ -245,7 +251,7 @@ const DataTableInternal = <TData,>({
245
251
  ],
246
252
  data,
247
253
  columns,
248
- meta: { rawData },
254
+ meta: { rawData, highlightPattern },
249
255
  getCoreRowModel: getCoreRowModel(),
250
256
  // pagination
251
257
  rowCount: totalRows === "too_many" ? undefined : totalRows,
@@ -1,5 +1,5 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- import React from "react";
2
+ import React, { useMemo } from "react";
3
3
  import { useDateFormatter, useLocale } from "react-aria";
4
4
  import { Tooltip } from "@/components/ui/tooltip";
5
5
 
@@ -45,6 +45,7 @@ export const DatePopover: React.FC<DatePopoverProps> = ({
45
45
 
46
46
  const TimezoneDisplay = ({ date }: { date: Date }) => {
47
47
  const { locale } = useLocale();
48
+ // The system timezone can change without the locale changing.
48
49
  const localTimezone = new Intl.DateTimeFormat(locale).resolvedOptions()
49
50
  .timeZone;
50
51
  const hasSubSeconds = date.getUTCMilliseconds() !== 0;
@@ -116,9 +117,10 @@ const RelativeTime = ({ date }: { date: Date }) => {
116
117
  const { locale } = useLocale();
117
118
 
118
119
  // Initialize relative time formatter with current locale and "auto" numeric style
119
- const relativeTimeFormatter = new Intl.RelativeTimeFormat(locale, {
120
- numeric: "auto",
121
- });
120
+ const relativeTimeFormatter = useMemo(
121
+ () => new Intl.RelativeTimeFormat(locale, { numeric: "auto" }),
122
+ [locale],
123
+ );
122
124
 
123
125
  const currentTime = new Date();
124
126
  // Get difference in seconds between now and the input date
@@ -24,6 +24,7 @@ import type {
24
24
  GetExportMetadata,
25
25
  } from "./schemas";
26
26
  import { useAsyncData } from "@/hooks/useAsyncData";
27
+ import { useStateForKey } from "@/hooks/useStateForKey";
27
28
  import { logNever } from "@/utils/assertNever";
28
29
  import { cn } from "@/utils/cn";
29
30
  import { copyToClipboard } from "@/utils/copy";
@@ -354,7 +355,6 @@ interface ExportContext {
354
355
  export const ExportActions: React.FC<ExportActionProps> = (props) => {
355
356
  const { getExportMetadata, metadataSource } = props;
356
357
  const [exportDialogOpen, setExportDialogOpen] = React.useState(false);
357
- const [failure, setFailure] = React.useState<ExportFailure | null>(null);
358
358
  const [settings, setSettings] =
359
359
  React.useState<ExportSettings>(DEFAULT_SETTINGS);
360
360
  const [exportContext, setExportContext] = React.useState<ExportContext>(
@@ -363,6 +363,10 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
363
363
  if (exportContext.source !== metadataSource) {
364
364
  setExportContext({ source: metadataSource });
365
365
  }
366
+ const [failure, setFailure] = useStateForKey<ExportFailure | null>(
367
+ exportContext,
368
+ () => null,
369
+ );
366
370
  const mounted = React.useRef(false);
367
371
  const isCurrentContext = useEvent(
368
372
  (context: ExportContext) => mounted.current && context === exportContext,
@@ -416,7 +420,6 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
416
420
  }, []);
417
421
  React.useEffect(() => {
418
422
  latestActionId.current += 1;
419
- setFailure(null);
420
423
  }, [metadataSource]);
421
424
  const policy = useAtomValue(downloadSizeLimitAtom);
422
425
  const overLimit = !!(
@@ -452,7 +455,6 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
452
455
  setExportDialogOpen(open);
453
456
  if (!open) {
454
457
  latestActionId.current += 1;
455
- setFailure(null);
456
458
  setExpandedFormat(null);
457
459
  setSettings(DEFAULT_SETTINGS);
458
460
  setGeometryColumn(null);
@@ -761,7 +763,10 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
761
763
  const expanded =
762
764
  configurableFormat !== null &&
763
765
  expandedFormat === configurableFormat;
766
+ const FormatHeader = configurableFormat ? "button" : "div";
764
767
  const panelId = `export-options-${option.format}`;
768
+ const descriptionId = `${panelId}-description`;
769
+ const summaryId = `${panelId}-summary`;
765
770
  const rowFailure =
766
771
  failure?.action.format === option.format ? failure : null;
767
772
  const summary =
@@ -802,55 +807,78 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
802
807
  data-testid={`export-row-${option.format}`}
803
808
  className={cn(expanded && "bg-muted/40")}
804
809
  >
805
- <div className="grid min-h-[60px] grid-cols-[28px_minmax(0,1fr)_auto] items-center gap-x-1 px-2.5 py-2 hover:bg-accent/50">
806
- {configurableFormat ? (
807
- <Button
808
- type="button"
809
- variant="ghost"
810
- size="icon"
811
- aria-label={`${optionLabel} options`}
812
- aria-expanded={expanded}
813
- aria-controls={panelId}
814
- onClick={() => toggleExpanded(option.format)}
810
+ <div className="grid grid-cols-[minmax(0,1fr)_auto] items-center hover:bg-accent/50">
811
+ <FormatHeader
812
+ type={configurableFormat ? "button" : undefined}
813
+ className={cn(
814
+ "grid min-h-[60px] min-w-0 grid-cols-[28px_minmax(0,1fr)] items-center gap-x-1 py-2 pl-2.5 pr-1 text-left",
815
+ configurableFormat &&
816
+ "cursor-pointer focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring",
817
+ )}
818
+ aria-label={
819
+ configurableFormat ? `${optionLabel} options` : undefined
820
+ }
821
+ aria-describedby={
822
+ configurableFormat
823
+ ? [descriptionId, summary && summaryId]
824
+ .filter(Boolean)
825
+ .join(" ")
826
+ : undefined
827
+ }
828
+ aria-expanded={configurableFormat ? expanded : undefined}
829
+ aria-controls={configurableFormat ? panelId : undefined}
830
+ onClick={
831
+ configurableFormat
832
+ ? () => toggleExpanded(option.format)
833
+ : undefined
834
+ }
835
+ >
836
+ <span
837
+ aria-hidden={true}
838
+ className="inline-flex h-6 w-6 items-center justify-center"
815
839
  >
816
- <ChevronRightIcon
817
- className={cn(
818
- "h-4 w-4 text-muted-foreground transition-transform",
819
- expanded && "rotate-90",
820
- )}
840
+ {configurableFormat && (
841
+ <ChevronRightIcon
842
+ className={cn(
843
+ "h-4 w-4 text-muted-foreground transition-transform",
844
+ expanded && "rotate-90",
845
+ )}
846
+ />
847
+ )}
848
+ </span>
849
+ <span className="flex min-w-0 items-center gap-3 pl-1">
850
+ <option.icon
851
+ aria-hidden={true}
852
+ data-testid={`export-format-icon-${option.format}`}
853
+ className="h-4 w-4 shrink-0 text-muted-foreground"
821
854
  />
822
- </Button>
823
- ) : (
824
- <span aria-hidden={true} className="h-6 w-6" />
825
- )}
826
- <div className="flex min-w-0 items-center gap-3 pl-1">
827
- <option.icon
828
- aria-hidden={true}
829
- data-testid={`export-format-icon-${option.format}`}
830
- className="h-4 w-4 shrink-0 text-muted-foreground"
831
- />
832
- <div className="grid min-w-0 gap-1">
833
- <span className="flex min-w-0 items-baseline gap-x-2">
834
- <span className="text-sm font-medium leading-tight">
835
- {optionLabel}
836
- </span>
837
- {summary && (
838
- <span
839
- data-testid={`export-summary-${option.format}`}
840
- className="min-w-0 truncate text-xs text-muted-foreground"
841
- >
842
- {summary}
855
+ <span className="grid min-w-0 gap-1">
856
+ <span className="flex min-w-0 items-baseline gap-x-2">
857
+ <span className="text-sm font-medium leading-tight">
858
+ {optionLabel}
843
859
  </span>
844
- )}
845
- </span>
846
- <span className="text-xs text-muted-foreground leading-tight">
847
- {option.format === "parquet" && hasGeometry
848
- ? "Parquet with geometry and CRS metadata"
849
- : option.description}
860
+ {summary && (
861
+ <span
862
+ id={summaryId}
863
+ data-testid={`export-summary-${option.format}`}
864
+ className="min-w-0 truncate text-xs text-muted-foreground"
865
+ >
866
+ {summary}
867
+ </span>
868
+ )}
869
+ </span>
870
+ <span
871
+ id={descriptionId}
872
+ className="text-xs text-muted-foreground leading-tight"
873
+ >
874
+ {option.format === "parquet" && hasGeometry
875
+ ? "Parquet with geometry and CRS metadata"
876
+ : option.description}
877
+ </span>
850
878
  </span>
851
- </div>
852
- </div>
853
- <div className="flex items-center gap-0.5">
879
+ </span>
880
+ </FormatHeader>
881
+ <div className="flex items-center gap-0.5 pr-2.5">
854
882
  <Tooltip
855
883
  content={
856
884
  option.canDownload
@@ -34,6 +34,7 @@ import { DelayMount } from "@/components/utils/delay-mount";
34
34
  import { useAsyncData } from "@/hooks/useAsyncData";
35
35
  import { Banner, ErrorBanner } from "@/plugins/impl/common/error-banner";
36
36
  import type { GetRowResult } from "@/plugins/impl/DataTablePlugin";
37
+ import { Objects } from "@/utils/objects";
37
38
  import { NAMELESS_COLUMN_PREFIX, renderCellValue } from "../columns";
38
39
  import { prettifyRowCount } from "../pagination";
39
40
  import {
@@ -88,8 +89,7 @@ export const RowViewerPanel: React.FC<RowViewerPanelProps> = ({
88
89
  handleRowSelectionChange?.((prev) => {
89
90
  if (isRowSelected) {
90
91
  // Remove this row from selection
91
- const { [rowIdx]: removedRow, ...rest } = prev;
92
- return rest;
92
+ return Objects.filter(prev, (_, key) => key !== String(rowIdx));
93
93
  }
94
94
  // Add this row to selection
95
95
  return { ...prev, [rowIdx]: true };
@@ -0,0 +1,79 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import type { ReactNode } from "react";
4
+
5
+ /** Half-open `[start, end)` offsets into a string. */
6
+ export type MatchRange = readonly [start: number, end: number];
7
+
8
+ /**
9
+ * Build the pattern that finds `query` in a display string. Returns
10
+ * `undefined` for an empty query. The query is matched as a literal,
11
+ * case-insensitive substring, which mirrors how the backend selects rows.
12
+ * The `u` flag selects Unicode simple case folding, the same folding the
13
+ * backend regex engines use.
14
+ */
15
+ export function buildHighlightPattern(query?: string): RegExp | undefined {
16
+ if (!query) {
17
+ return undefined;
18
+ }
19
+ const escaped = query.replace(/[$()*+.?[\\\]^{|}]/g, "\\$&");
20
+ return new RegExp(escaped, "giu");
21
+ }
22
+
23
+ /** Offsets of every match of `pattern` in `text`. */
24
+ export function findMatchRanges(text: string, pattern?: RegExp): MatchRange[] {
25
+ if (!pattern) {
26
+ return [];
27
+ }
28
+ const ranges: MatchRange[] = [];
29
+ for (const match of text.matchAll(pattern)) {
30
+ ranges.push([match.index, match.index + match[0].length]);
31
+ }
32
+ return ranges;
33
+ }
34
+
35
+ /**
36
+ * Wrap the parts of `text` covered by `ranges` in `<mark>` elements.
37
+ * `ranges` are offsets into a larger string in which `text` starts at
38
+ * `offset`, so one set of ranges can be shared across adjacent segments.
39
+ * Returns `text` unchanged when no range overlaps it.
40
+ */
41
+ export function highlightRanges(
42
+ text: string,
43
+ ranges: readonly MatchRange[],
44
+ offset = 0,
45
+ ): ReactNode {
46
+ const segments: ReactNode[] = [];
47
+ let cursor = 0;
48
+ for (const [rangeStart, rangeEnd] of ranges) {
49
+ const start = Math.max(rangeStart - offset, cursor);
50
+ const end = Math.min(rangeEnd - offset, text.length);
51
+ if (end <= start) {
52
+ continue;
53
+ }
54
+ if (start > cursor) {
55
+ segments.push(text.slice(cursor, start));
56
+ }
57
+ segments.push(
58
+ <mark key={start} className="bg-search-highlight text-inherit">
59
+ {text.slice(start, end)}
60
+ </mark>,
61
+ );
62
+ cursor = end;
63
+ }
64
+ if (segments.length === 0) {
65
+ return text;
66
+ }
67
+ if (cursor < text.length) {
68
+ segments.push(text.slice(cursor));
69
+ }
70
+ return segments;
71
+ }
72
+
73
+ /**
74
+ * Wrap every match of `pattern` in `text` in a `<mark>` element. Returns
75
+ * `text` unchanged when `pattern` is absent or does not match.
76
+ */
77
+ export function highlightMatches(text: string, pattern?: RegExp): ReactNode {
78
+ return highlightRanges(text, findMatchRanges(text, pattern));
79
+ }
@@ -6,6 +6,7 @@ import type { DataType } from "@/core/kernel/messages";
6
6
  declare module "@tanstack/react-table" {
7
7
  interface TableMeta<TData extends RowData> {
8
8
  rawData?: TData[]; // raw data for filtering/copying (present only if format_mapping is provided)
9
+ highlightPattern?: RegExp; // built from the committed search query that produced the current rows
9
10
  }
10
11
  }
11
12
 
@@ -1,7 +1,7 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import { marked } from "marked";
4
- import { useState } from "react";
4
+ import { Fragment, type ReactNode, useState } from "react";
5
5
  import { MarkdownRenderer } from "@/components/markdown/markdown-renderer";
6
6
  import {
7
7
  Popover,
@@ -10,13 +10,20 @@ import {
10
10
  } from "@/components/ui/popover";
11
11
  import { Events } from "@/utils/events";
12
12
  import type { ContentPart } from "@/utils/url-parser";
13
+ import { findMatchRanges, highlightRanges } from "./search-highlight";
13
14
 
14
- const ImageWithFallback = ({ url }: { url: string }) => {
15
+ const ImageWithFallback = ({
16
+ url,
17
+ fallbackLabel,
18
+ }: {
19
+ url: string;
20
+ fallbackLabel?: ReactNode;
21
+ }) => {
15
22
  const [error, setError] = useState(false);
16
23
  const [open, setOpen] = useState(false);
17
24
 
18
25
  if (error) {
19
- return <URLAnchor url={url} />;
26
+ return <URLAnchor url={url}>{fallbackLabel}</URLAnchor>;
20
27
  }
21
28
 
22
29
  return (
@@ -93,21 +100,54 @@ export const MarkdownUrlDetector = ({
93
100
  return <UrlDetector parts={parts} />;
94
101
  };
95
102
 
96
- export const UrlDetector = ({ parts }: { parts: ContentPart[] }) => {
103
+ export const UrlDetector = ({
104
+ parts,
105
+ highlightPattern,
106
+ }: {
107
+ parts: ContentPart[];
108
+ highlightPattern?: RegExp;
109
+ }) => {
110
+ // Match against the whole string so a query that spans a text part and
111
+ // a link is still marked. Each part then takes its slice of the ranges.
112
+ const ranges = highlightPattern
113
+ ? findMatchRanges(parts.map(partText).join(""), highlightPattern)
114
+ : [];
115
+
116
+ let offset = 0;
97
117
  const markup = parts.map((part, idx) => {
118
+ const text = partText(part);
119
+ const label = highlightRanges(text, ranges, offset);
120
+ offset += text.length;
121
+
98
122
  if (part.type === "url") {
99
- return <URLAnchor key={idx} url={part.url} />;
123
+ return (
124
+ <URLAnchor key={idx} url={part.url}>
125
+ {label}
126
+ </URLAnchor>
127
+ );
100
128
  }
101
129
  if (part.type === "image") {
102
- return <ImageWithFallback key={idx} url={part.url} />;
130
+ return (
131
+ <ImageWithFallback key={idx} url={part.url} fallbackLabel={label} />
132
+ );
103
133
  }
104
- return part.value;
134
+ return <Fragment key={idx}>{label}</Fragment>;
105
135
  });
106
136
 
107
137
  return markup;
108
138
  };
109
139
 
110
- const URLAnchor = ({ url }: { url: string }) => {
140
+ function partText(part: ContentPart): string {
141
+ return part.type === "text" ? part.value : part.url;
142
+ }
143
+
144
+ const URLAnchor = ({
145
+ url,
146
+ children,
147
+ }: {
148
+ url: string;
149
+ children?: ReactNode;
150
+ }) => {
111
151
  return (
112
152
  <a
113
153
  href={url}
@@ -116,7 +156,7 @@ const URLAnchor = ({ url }: { url: string }) => {
116
156
  onClick={Events.stopPropagation()}
117
157
  className="text-link hover:underline"
118
158
  >
119
- {url}
159
+ {children ?? url}
120
160
  </a>
121
161
  );
122
162
  };
@@ -0,0 +1,97 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import {
4
+ fireEvent,
5
+ render,
6
+ screen,
7
+ waitFor,
8
+ within,
9
+ } from "@testing-library/react";
10
+ import { createStore, Provider } from "jotai";
11
+ import { beforeAll, describe, expect, it, vi } from "vitest";
12
+ import { MockRequestClient } from "@/__mocks__/requests";
13
+ import { Command, CommandList } from "@/components/ui/command";
14
+ import { TooltipProvider } from "@/components/ui/tooltip";
15
+ import type { DataTable } from "@/core/kernel/messages";
16
+ import { requestClientAtom } from "@/core/network/requests";
17
+ import { copyToClipboard } from "@/utils/copy";
18
+ import { TableList } from "../datasources";
19
+
20
+ vi.mock("@/utils/copy", () => ({
21
+ copyToClipboard: vi.fn().mockResolvedValue(undefined),
22
+ }));
23
+
24
+ beforeAll(() => {
25
+ HTMLElement.prototype.scrollIntoView = vi.fn();
26
+ });
27
+
28
+ const table: DataTable = {
29
+ name: "users",
30
+ columns: [
31
+ {
32
+ name: "email",
33
+ type: "string",
34
+ external_type: "VARCHAR",
35
+ sample_values: [],
36
+ },
37
+ {
38
+ name: "name",
39
+ type: "string",
40
+ external_type: "VARCHAR",
41
+ sample_values: [],
42
+ },
43
+ ],
44
+ source: "memory",
45
+ source_type: "local",
46
+ type: "table",
47
+ engine: null,
48
+ indexes: null,
49
+ num_columns: 2,
50
+ num_rows: null,
51
+ variable_name: null,
52
+ primary_keys: null,
53
+ };
54
+
55
+ describe("Column actions", () => {
56
+ it("copies each column through one named button without expanding its preview", async () => {
57
+ const store = createStore();
58
+ const client = MockRequestClient.create();
59
+ store.set(requestClientAtom, client);
60
+ render(
61
+ <Provider store={store}>
62
+ <TooltipProvider>
63
+ <Command>
64
+ <CommandList>
65
+ <TableList tables={[table]} />
66
+ </CommandList>
67
+ </Command>
68
+ </TooltipProvider>
69
+ </Provider>,
70
+ );
71
+
72
+ fireEvent.click(await screen.findByText("users"));
73
+
74
+ for (const column of table.columns) {
75
+ const copyButton = await screen.findByRole("button", {
76
+ name: `Copy column name ${column.name}`,
77
+ });
78
+ const row = screen.getByRole("option", {
79
+ name: `${column.name} ${column.external_type}`,
80
+ });
81
+ expect(within(row).getAllByRole("button")).toEqual([copyButton]);
82
+ expect(copyButton.querySelector("button")).toBeNull();
83
+
84
+ fireEvent.focus(copyButton);
85
+ expect(
86
+ await screen.findByRole("tooltip", { name: "Copy column name" }),
87
+ ).toBeInTheDocument();
88
+
89
+ fireEvent.click(copyButton);
90
+ await waitFor(() => {
91
+ expect(copyToClipboard).toHaveBeenLastCalledWith(column.name);
92
+ });
93
+ expect(client.previewDatasetColumn).not.toHaveBeenCalled();
94
+ }
95
+ expect(copyToClipboard).toHaveBeenCalledTimes(2);
96
+ });
97
+ });