@marimo-team/frontend 0.23.17-dev5 → 0.23.17-dev50

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 (326) hide show
  1. package/dist/assets/{CellStatus-DIW20_Z8.js → CellStatus-XlxL4ctu.js} +1 -1
  2. package/dist/assets/{ConnectedDataExplorerComponent-C4zEgCZh.js → ConnectedDataExplorerComponent-BF5CJvB2.js} +1 -1
  3. package/dist/assets/{ErrorBoundary-DzTD8LVq.js → ErrorBoundary-BfMsWVQU.js} +1 -1
  4. package/dist/assets/ImageComparisonComponent-CukPDP1p.js +1 -0
  5. package/dist/assets/{ImperativeModal-fY-ZfT0b.js → ImperativeModal-Ci0icDPt.js} +1 -1
  6. package/dist/assets/{JsonOutput-Bk2exFh0.js → JsonOutput-W3f6H6VQ.js} +13 -13
  7. package/dist/assets/{LazyAnyLanguageCodeMirror-A-Ant1Ad.js → LazyAnyLanguageCodeMirror-Dcvy_p3-.js} +2 -2
  8. package/dist/assets/{MarimoErrorOutput-L_o2Knzo.js → MarimoErrorOutput-COZo8cfk.js} +5 -5
  9. package/dist/assets/{RSPContexts-WiCgGm__.js → RSPContexts-IrDQoFki.js} +1 -1
  10. package/dist/assets/{RenderHTML-BTDLQMKg.js → RenderHTML-CQC8I348.js} +1 -1
  11. package/dist/assets/{RunButton-DiHX9YjB.js → RunButton-DRxV8u_T.js} +1 -1
  12. package/dist/assets/add-cell-with-ai-CuLILhtV.js +107 -0
  13. package/dist/assets/{add-connection-dialog-BZaJAQCM.js → add-connection-dialog-Dld_PDdZ.js} +31 -31
  14. package/dist/assets/{agent-panel-CRxiqCgP.js → agent-panel-3Ta5EPgw.js} +6 -6
  15. package/dist/assets/ai-model-dropdown-CIqhiHxf.js +5 -0
  16. package/dist/assets/{alert-dialog-Bb8Lz8ko.js → alert-dialog-DMh8_LWc.js} +1 -1
  17. package/dist/assets/{any-language-editor-BIeaxtdD.js → any-language-editor-DUQN91an.js} +1 -1
  18. package/dist/assets/{app-config-button-BS4LSo1l.js → app-config-button-EyzXs1Nt.js} +1 -1
  19. package/dist/assets/{blob-B9eg-RfN.js → blob-BzgGkt3B.js} +1 -1
  20. package/dist/assets/{button-DDGiD1NW.js → button-BB1AV4Rb.js} +1 -1
  21. package/dist/assets/{cache-panel-B2B1ePao.js → cache-panel-hpL5o4Y7.js} +1 -1
  22. package/dist/assets/{capabilities-DoEg0XwL.js → capabilities-LI6_PoMn.js} +1 -1
  23. package/dist/assets/{cell-editor-OC7h_nZ3.js → cell-editor-C5qD14SG.js} +11 -11
  24. package/dist/assets/{cell-link-D0aELsL_.js → cell-link-uPmFMRTh.js} +1 -1
  25. package/dist/assets/cells-BE1kpmPF.css +2 -0
  26. package/dist/assets/{cells-DLYs3NiJ.js → cells-DxL32Atg.js} +60 -60
  27. package/dist/assets/chat-display-CBpsXLHT.js +1 -0
  28. package/dist/assets/chat-panel-Dzq-QTrj.js +4 -0
  29. package/dist/assets/chat-ui-BcI4PAQ1.js +4 -0
  30. package/dist/assets/{column-preview-CnqeJb-x.js → column-preview-Dxxs9Brz.js} +1 -1
  31. package/dist/assets/{command-Dqxie4gq.js → command-CPFHYoYk.js} +1 -1
  32. package/dist/assets/command-palette-B1EO8oVR.js +1 -0
  33. package/dist/assets/{common-D_beLbpm.js → common-aHaDUD0-.js} +1 -1
  34. package/dist/assets/components-Bm1d4vm9.js +1 -0
  35. package/dist/assets/{components-CxEGXaTB.js → components-RV25kGIW.js} +1 -1
  36. package/dist/assets/config-BVfo0E1-.js +1 -0
  37. package/dist/assets/config-Bm5B9wGP.js +1 -0
  38. package/dist/assets/{copy-Bxb1_Gtw.js → copy-B-LLR1Yy.js} +1 -1
  39. package/dist/assets/{copy-icon-neDQUo4Q.js → copy-icon-DWG8Y-xW.js} +1 -1
  40. package/dist/assets/{createReducer-DGx5Af-C.js → createReducer-DNehfY0t.js} +1 -1
  41. package/dist/assets/{datasource-Bfg8PKXp.js → datasource-D0eBPOXe.js} +2 -2
  42. package/dist/assets/{dates-C2LWzsCZ.js → dates-BzOh4kS_.js} +1 -1
  43. package/dist/assets/defaultLocale-t77eoiwF.js +1 -0
  44. package/dist/assets/{dependency-graph-panel-CB5ie0zu.js → dependency-graph-panel-BKWUzXHX.js} +2 -2
  45. package/dist/assets/{diagram-PSM6KHXK-DIXxW5au.js → diagram-PSM6KHXK-CrTUa1Xy.js} +1 -1
  46. package/dist/assets/{dialog-BCVepWLJ.js → dialog-88bLE398.js} +1 -1
  47. package/dist/assets/{dist-CPjTeT8p.js → dist-6qzebh8f.js} +1 -1
  48. package/dist/assets/dist-BLqRx0W-.js +1 -0
  49. package/dist/assets/{dist-BP4hpPAE.js → dist-BVr4HQPR.js} +1 -1
  50. package/dist/assets/{dist-DnjsPBRJ.js → dist-BWXIz8tq.js} +1 -1
  51. package/dist/assets/{dist-Cros4IAo.js → dist-BWbS__vR.js} +1 -1
  52. package/dist/assets/dist-BZehnCk2.js +1 -0
  53. package/dist/assets/dist-BaRgF7DU.js +1 -0
  54. package/dist/assets/{dist-DYEuVkEX.js → dist-BblMEWTQ.js} +1 -1
  55. package/dist/assets/dist-BlMeFaGB.js +1 -0
  56. package/dist/assets/{dist-CrKffqgx.js → dist-BmYa2llD.js} +1 -1
  57. package/dist/assets/{dist-9o0Xjd5v.js → dist-Btp8vRFr.js} +1 -1
  58. package/dist/assets/{dist-C-3FVUQA.js → dist-BvMWjDxm.js} +1 -1
  59. package/dist/assets/{dist-DeJBvhAc.js → dist-ByfZ_2N7.js} +1 -1
  60. package/dist/assets/dist-C-g2AKOo.js +1 -0
  61. package/dist/assets/{dist-DQl27SFq.js → dist-C52BqSeO.js} +1 -1
  62. package/dist/assets/{dist-CiVShZKM.js → dist-C7rDfTPJ.js} +1 -1
  63. package/dist/assets/{dist-BKgHjzML.js → dist-C830fIIj.js} +1 -1
  64. package/dist/assets/{dist-fLBYOt9R.js → dist-CHN_sQZw.js} +1 -1
  65. package/dist/assets/dist-CLFhXj8x.js +1 -0
  66. package/dist/assets/{dist-D47O3FBX.js → dist-CLQWOmD7.js} +1 -1
  67. package/dist/assets/dist-CO4dulTL.js +1 -0
  68. package/dist/assets/dist-CPw3iq_l.js +1 -0
  69. package/dist/assets/dist-CQtN-vVI.js +1 -0
  70. package/dist/assets/dist-CZE9FE_5.js +1 -0
  71. package/dist/assets/dist-CexuM4NA.js +1 -0
  72. package/dist/assets/{dist-DJuRuTXr.js → dist-CfiLpZZj.js} +1 -1
  73. package/dist/assets/dist-CjSt6gRp.js +1 -0
  74. package/dist/assets/dist-Cr4q2U0d.js +1 -0
  75. package/dist/assets/{dist-C6_DArtj.js → dist-CxaoIFrU.js} +1 -1
  76. package/dist/assets/{dist-aRc_dRed.js → dist-D5nYeU7Q.js} +1 -1
  77. package/dist/assets/{dist-BJZAIIze.js → dist-DX04AOlt.js} +1 -1
  78. package/dist/assets/dist-DkZos4_s.js +1 -0
  79. package/dist/assets/{dist-DI1-Z60I.js → dist-DmCNk9gq.js} +1 -1
  80. package/dist/assets/{dist-kLs0VbW_.js → dist-DmjT6iaH.js} +1 -1
  81. package/dist/assets/{dist-CvWC93qL.js → dist-DrY8fZeD.js} +1 -1
  82. package/dist/assets/{dist-l4RTzHX-.js → dist-Drexw1Ir.js} +1 -1
  83. package/dist/assets/{dist-DrBqBDHF.js → dist-DtNSqHzW.js} +1 -1
  84. package/dist/assets/dist-DxQA7Dn1.js +1 -0
  85. package/dist/assets/{dist-BLN8PNJV.js → dist-Dza6FJtV.js} +1 -1
  86. package/dist/assets/{dist-8SH6SWEG.js → dist-HrXjBuXP.js} +1 -1
  87. package/dist/assets/dist-J00F7mRY.js +1 -0
  88. package/dist/assets/{dist-DcwTQG1A.js → dist-Jbd9zUbK.js} +1 -1
  89. package/dist/assets/{dist-CZvbHnyT.js → dist-bvfx5OQE.js} +1 -1
  90. package/dist/assets/{dist-12S8I4CH.js → dist-gUHullA_.js} +11 -11
  91. package/dist/assets/dist-nYKCNtEX.js +1 -0
  92. package/dist/assets/dist-oK8dffBy.js +1 -0
  93. package/dist/assets/{documentation-panel-Dii7ouzj.js → documentation-panel-DotG4o5W.js} +1 -1
  94. package/dist/assets/{download-DenADH0w.js → download-Tklnz6pn.js} +3 -3
  95. package/dist/assets/{dropdown-menu-dHmZR8bI.js → dropdown-menu-BGBlPKNz.js} +1 -1
  96. package/dist/assets/{edit-page-B0EhuiM-.js → edit-page-HdLqqfXC.js} +6 -6
  97. package/dist/assets/{error-banner-BSxEOIn2.js → error-banner-BkIr-0dH.js} +1 -1
  98. package/dist/assets/{error-entries-4XYBryls.js → error-entries-BvUR_22-.js} +3 -3
  99. package/dist/assets/{error-panel-xYZ4lX0A.js → error-panel-v0YdlA0h.js} +1 -1
  100. package/dist/assets/{errors-PgHhDa-M.js → errors-DCPV979Q.js} +1 -1
  101. package/dist/assets/{es-gYgfF8y5.js → es-Dl2xAwTv.js} +1 -1
  102. package/dist/assets/{esm-B30N1GS0.js → esm-Bmzgrl_5.js} +1 -1
  103. package/dist/assets/{esm-BDGraAqp.js → esm-Nb9m4lqg.js} +1 -1
  104. package/dist/assets/{field-BsxD35VE.js → field-BFgvUZyh.js} +1 -1
  105. package/dist/assets/file-explorer-panel-DSZY0Q7h.js +37 -0
  106. package/dist/assets/{file-icons-mgXTzM1c.js → file-icons-NkObNkr_.js} +1 -1
  107. package/dist/assets/{file-name-input-Dq7Z_QmU.js → file-name-input-BQJ9U9lU.js} +1 -1
  108. package/dist/assets/{floating-outline-ypOHt2wD.js → floating-outline-BHmregl1.js} +1 -1
  109. package/dist/assets/{focus-0L1nX7M9.js → focus-BBiJct2G.js} +1 -1
  110. package/dist/assets/{form-oz4Z280Z.js → form-BGoLLcgk.js} +1 -1
  111. package/dist/assets/{form-BsW7zr4t.js → form-BIUQB1NI.js} +1 -1
  112. package/dist/assets/{formats-DzmLpc9A.js → formats-DNYtnQk7.js} +1 -1
  113. package/dist/assets/{formatting-COz4XEaB.js → formatting-qNOrQYRN.js} +1 -1
  114. package/dist/assets/{fullscreen-CTDqNQr6.js → fullscreen-B51geQ_6.js} +1 -1
  115. package/dist/assets/{gallery-page-DbuxknAw.js → gallery-page-DBHHOraF.js} +1 -1
  116. package/dist/assets/{ganttDiagram-JELNMOA3-DaS60lKW.js → ganttDiagram-JELNMOA3-C9KBLmAA.js} +1 -1
  117. package/dist/assets/{glide-data-editor-CejKKgnN.js → glide-data-editor-CHN4-xUu.js} +1 -1
  118. package/dist/assets/{home-page-BOP1dDcM.js → home-page-DLeE6pH_.js} +2 -2
  119. package/dist/assets/{html-to-image-D_C-hjtC.js → html-to-image-ChNWEUlR.js} +1 -1
  120. package/dist/assets/index-BxVymcMs.js +38 -0
  121. package/dist/assets/index-WoaGtlKd.css +2 -0
  122. package/dist/assets/{input-JxFwu-3G.js → input-_AaMXWgD.js} +1 -1
  123. package/dist/assets/{kiosk-mode-BvKMuigM.js → kiosk-mode-RTwj6XH4.js} +1 -1
  124. package/dist/assets/{label-CqjrpwRC.js → label-DUD3SZnk.js} +1 -1
  125. package/dist/assets/{layout-yfmf2ZBx.js → layout-QkEddeXT.js} +4 -4
  126. package/dist/assets/{linear-BPv4krGt.js → linear-B1ROUUtm.js} +1 -1
  127. package/dist/assets/{links-ClNQSIal.js → links-DBc28bsl.js} +1 -1
  128. package/dist/assets/{logs-panel-OMd1Z9CL.js → logs-panel-Cdcjc_fq.js} +1 -1
  129. package/dist/assets/markdown-export-CNu5ZTPR.js +1 -0
  130. package/dist/assets/{markdown-renderer-BfLNJGNj.js → markdown-renderer-DpskAUxO.js} +1 -1
  131. package/dist/assets/{menu-items-CCi6aOPm.js → menu-items-Db-bgz92.js} +1 -1
  132. package/dist/assets/{mermaid-B8gNHyGy.js → mermaid-OU2hLna0.js} +3 -3
  133. package/dist/assets/{name-cell-input-COtjRghz.js → name-cell-input-DoxL2EXI.js} +1 -1
  134. package/dist/assets/{numbers-rmawRqQP.js → numbers-D9g2ArIv.js} +1 -1
  135. package/dist/assets/{outline-panel-CIGf4SLb.js → outline-panel-BxjI-YSj.js} +1 -1
  136. package/dist/assets/{packages-panel-DLuMuNuG.js → packages-panel-CiKYP9Ij.js} +1 -1
  137. package/dist/assets/{pair-with-agent-modal-C5OhJ8rX.js → pair-with-agent-modal-1b5Eh8Xo.js} +1 -1
  138. package/dist/assets/{panel-accordion-state-D1UP17Lk.js → panel-accordion-state-BkWYwckU.js} +1 -1
  139. package/dist/assets/{panels-B0w23XJS.js → panels-Swk0l08v.js} +2 -2
  140. package/dist/assets/{popover-Bcz1Zikb.js → popover-DHbixh3D.js} +1 -1
  141. package/dist/assets/{precisionRound-i2ImZxgc.js → precisionRound-DORAUfVY.js} +1 -1
  142. package/dist/assets/process-output-DJonUYp2.js +1 -0
  143. package/dist/assets/{quadrantDiagram-AYHSOK5B-DJuGB95-.js → quadrantDiagram-AYHSOK5B-Duf8sGaa.js} +1 -1
  144. package/dist/assets/{radio-group-BGXYy3u5.js → radio-group-ByLzIKjm.js} +1 -1
  145. package/dist/assets/react-vega-J-J2akIt.js +191 -0
  146. package/dist/assets/{readonly-code-display-DdABj8tH.js → readonly-code-display-B_ZHScA7.js} +1 -1
  147. package/dist/assets/{readonly-python-code-DUjqXwhi.js → readonly-python-code-C4GeGU7O.js} +1 -1
  148. package/dist/assets/{renderShortcut-Bl8_FVn8.js → renderShortcut-DnqsU4zj.js} +1 -1
  149. package/dist/assets/{reveal-component-BjgfnO81.js → reveal-component-De_cjq3p.js} +2 -2
  150. package/dist/assets/{run-page-DDty5GvQ.js → run-page-DmXT_-MQ.js} +1 -1
  151. package/dist/assets/{runs-s9t2f_Tg.js → runs-C6avq6Gi.js} +1 -1
  152. package/dist/assets/scratchpad-panel-BtGHIRdJ.js +1 -0
  153. package/dist/assets/{scroll-to-bottom-button-C8yfT4wq.js → scroll-to-bottom-button-CRMPUjYZ.js} +1 -1
  154. package/dist/assets/{secrets-panel-Cz17_MLf.js → secrets-panel-DmAwH7vI.js} +1 -1
  155. package/dist/assets/{select-BP75_5jJ.js → select-Clmc8Hw2.js} +1 -1
  156. package/dist/assets/{session-BmukRjSi.js → session-BtBr2zr3.js} +1 -1
  157. package/dist/assets/{session-panel-aMoQPTCB.js → session-panel-kBLydN0h.js} +1 -1
  158. package/dist/assets/{share-852X_cqy.js → share-DSOv_SvY.js} +1 -1
  159. package/dist/assets/{skeleton-CPuDx7yn.js → skeleton-CJymG2v3.js} +1 -1
  160. package/dist/assets/{snippets-panel-DBDXr-XP.js → snippets-panel-l_HnIWUj.js} +1 -1
  161. package/dist/assets/{spec-C7opZZRp.js → spec-CdDzA5ef.js} +1 -1
  162. package/dist/assets/{state-DCtLlFJq.js → state-B4lC29et.js} +1 -1
  163. package/dist/assets/{state-C4kiwedg.js → state-BkoVfBqt.js} +1 -1
  164. package/dist/assets/{state-hlEqpZfH.js → state-D9xpDCxa.js} +2 -2
  165. package/dist/assets/state-DDimGh2o.js +1 -0
  166. package/dist/assets/{strings--BknHX9C.js → strings-DMKwTex-.js} +1 -1
  167. package/dist/assets/{swiper-component-DXW8snlt.js → swiper-component-pSAP3cRf.js} +1 -1
  168. package/dist/assets/switch-8FxRs-OU.js +1 -0
  169. package/dist/assets/{terminal-CaHTVNrz.js → terminal-DPQJ6hNg.js} +1 -1
  170. package/dist/assets/{textarea-DtCcemo9.js → textarea-BzWvGIrJ.js} +1 -1
  171. package/dist/assets/{time-p47i6wvp.js → time-BO-naW1O.js} +1 -1
  172. package/dist/assets/tooltip-BMi4WFDl.js +61 -0
  173. package/dist/assets/{tooltip-4bZlU7jl.js → tooltip-BcgbFR7q.js} +1 -1
  174. package/dist/assets/{tracing-CStM7RGI.js → tracing-DCuWLKnk.js} +1 -1
  175. package/dist/assets/{tracing-panel-BTLzu8L-.js → tracing-panel-dVtThkzL.js} +2 -2
  176. package/dist/assets/{tree-actions-BwwVyp3l.js → tree-actions-o2iyMmKN.js} +1 -1
  177. package/dist/assets/{useBoolean-DIi7jQLa.js → useBoolean-sASJEpGA.js} +1 -1
  178. package/dist/assets/{useCellActionButton-uxVyv3JK.js → useCellActionButton-n2-2xb-5.js} +1 -1
  179. package/dist/assets/{useDeleteCell-Cy23OP4e.js → useDeleteCell-DH0weyin.js} +1 -1
  180. package/dist/assets/{useDependencyPanelTab-DNfjp_-X.js → useDependencyPanelTab-DQZmbkd-.js} +1 -1
  181. package/dist/assets/{useEventListener-C0195JsF.js → useEventListener-B_wSLgFR.js} +1 -1
  182. package/dist/assets/{useHotkey-DLZvrRZT.js → useHotkey-DJyYmk5P.js} +1 -1
  183. package/dist/assets/{useIframeCapabilities-j6fR_dBA.js → useIframeCapabilities-D_6fB_8O.js} +1 -1
  184. package/dist/assets/{useInstallPackage-WiYBwhUO.js → useInstallPackage-DyNrAhrx.js} +1 -1
  185. package/dist/assets/{useInterval-CnQfyMa9.js → useInterval-CjGsg4ie.js} +1 -1
  186. package/dist/assets/useNotebookActions-BUQta3nX.js +12 -0
  187. package/dist/assets/useRunCells-8xYRh7wH.js +1 -0
  188. package/dist/assets/{useSplitCell-BK5mnkkL.js → useSplitCell-BvgvIZaz.js} +1 -1
  189. package/dist/assets/{useTheme-Bxv9B31g.js → useTheme-BR31Gjbc.js} +1 -1
  190. package/dist/assets/utils-C5VWChv9.js +1 -0
  191. package/dist/assets/{vega-component-BGiCExka.js → vega-component-DG9G1qk2.js} +1 -1
  192. package/dist/assets/{vega-embed-container-DxSqUOCK.js → vega-embed-container-BJRQVehC.js} +1 -1
  193. package/dist/assets/vega-loader.browser-CAfmqKEc.js +6 -0
  194. package/dist/assets/{write-secret-modal-Dtm66yVf.js → write-secret-modal-DwCUuNRy.js} +1 -1
  195. package/dist/assets/{xychartDiagram-PRI3JC2R-B8A0hi-Q.js → xychartDiagram-PRI3JC2R-ClPpSFDI.js} +1 -1
  196. package/dist/assets/{zod-H_cgTO0M.js → zod-CIOjj3iA.js} +5 -5
  197. package/dist/index.html +87 -87
  198. package/package.json +7 -7
  199. package/src/__mocks__/requests.ts +5 -0
  200. package/src/components/ai/ai-model-dropdown.tsx +2 -2
  201. package/src/components/app-config/user-config-form.tsx +26 -0
  202. package/src/components/chat/__tests__/capabilities-popover.test.tsx +38 -0
  203. package/src/components/chat/__tests__/chat-abort.test.ts +51 -0
  204. package/src/components/chat/__tests__/use-chat-controller-id.test.tsx +39 -0
  205. package/src/components/chat/capabilities-popover.tsx +68 -0
  206. package/src/components/chat/chat-abort.ts +44 -0
  207. package/src/components/chat/chat-display.tsx +17 -0
  208. package/src/components/chat/chat-panel.tsx +107 -50
  209. package/src/components/chat/chat-utils.ts +1 -1
  210. package/src/components/chat/use-chat-controller-id.ts +24 -0
  211. package/src/components/data-table/charts/__tests__/schemas.test.ts +35 -0
  212. package/src/components/data-table/charts/schemas.ts +5 -2
  213. package/src/components/databases/icons/huggingface.svg +8 -0
  214. package/src/components/datasets/__tests__/icons.test.ts +21 -0
  215. package/src/components/datasets/icons.tsx +10 -0
  216. package/src/components/datasources/__tests__/components.test.tsx +29 -0
  217. package/src/components/datasources/components.tsx +8 -3
  218. package/src/components/editor/actions/export-dialog/__tests__/export-dialog.test.tsx +35 -0
  219. package/src/components/editor/actions/export-dialog/__tests__/state.test.ts +61 -0
  220. package/src/components/editor/actions/export-dialog/export-command.ts +4 -9
  221. package/src/components/editor/actions/export-dialog/export-dialog.tsx +27 -23
  222. package/src/components/editor/actions/export-dialog/format-notice.tsx +28 -0
  223. package/src/components/editor/actions/export-dialog/state.ts +23 -5
  224. package/src/components/editor/actions/export-dialog/use-export-dialog.ts +7 -15
  225. package/src/components/editor/actions/useConfigActions.tsx +24 -0
  226. package/src/components/editor/ai/add-cell-with-ai.tsx +2 -2
  227. package/src/components/editor/ai/ai-completion-editor.tsx +2 -2
  228. package/src/components/editor/chrome/panels/outline/__tests__/useActiveOutline.test.ts +26 -0
  229. package/src/components/editor/chrome/wrapper/useOpenAiAssistant.ts +2 -2
  230. package/src/components/editor/code/__tests__/readonly-python-code.test.tsx +114 -2
  231. package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +14 -0
  232. package/src/components/editor/connections/storage/__tests__/as-code.test.ts +20 -0
  233. package/src/components/editor/connections/storage/add-storage-form.tsx +10 -0
  234. package/src/components/editor/connections/storage/as-code.ts +19 -1
  235. package/src/components/editor/connections/storage/schemas.ts +19 -0
  236. package/src/components/storage/__tests__/storage-snippets.test.ts +88 -0
  237. package/src/components/storage/components.tsx +2 -0
  238. package/src/components/storage/storage-snippets.ts +58 -0
  239. package/src/core/__tests__/mode.test.ts +85 -0
  240. package/src/core/ai/__tests__/config.test.tsx +96 -0
  241. package/src/core/ai/config.ts +16 -22
  242. package/src/core/ai/constants.ts +7 -0
  243. package/src/core/ai/staged-cells.ts +3 -0
  244. package/src/core/ai/state.ts +10 -0
  245. package/src/core/codemirror/__tests__/setup.test.ts +1 -1
  246. package/src/core/codemirror/cm.ts +2 -2
  247. package/src/core/codemirror/code-lens/__tests__/extension.test.ts +4 -5
  248. package/src/core/codemirror/code-lens/extension.ts +3 -3
  249. package/src/core/codemirror/find-replace/search-highlight.ts +8 -3
  250. package/src/core/codemirror/language/languages/sql/__tests__/renderers.test.tsx +44 -0
  251. package/src/core/codemirror/language/languages/sql/renderers.tsx +3 -3
  252. package/src/core/codemirror/theme/__tests__/light.test.ts +59 -123
  253. package/src/core/codemirror/theme/dark.ts +6 -5
  254. package/src/core/codemirror/theme/light.ts +6 -4
  255. package/src/core/config/__tests__/config-schema.test.ts +2 -0
  256. package/src/core/config/config-schema.ts +1 -0
  257. package/src/core/config/feature-flag.tsx +0 -2
  258. package/src/core/dom/outline.ts +25 -2
  259. package/src/core/hotkeys/hotkeys.ts +2 -0
  260. package/src/core/mode.ts +11 -9
  261. package/src/core/network/__tests__/api.test.ts +132 -17
  262. package/src/core/network/__tests__/export-filename.test.ts +43 -0
  263. package/src/core/network/__tests__/requests-lazy.test.ts +1 -0
  264. package/src/core/network/api.ts +47 -12
  265. package/src/core/network/export-filename.ts +37 -0
  266. package/src/core/network/markdown-export.ts +19 -0
  267. package/src/core/network/requests-network.ts +29 -5
  268. package/src/core/network/types.ts +4 -0
  269. package/src/core/storage/types.ts +1 -0
  270. package/src/css/app/codemirror.css +11 -7
  271. package/src/css/globals.css +8 -0
  272. package/src/plugins/impl/DataEditorPlugin.tsx +2 -9
  273. package/src/plugins/impl/__tests__/DataEditorPlugin.test.ts +18 -0
  274. package/src/plugins/impl/chat/chat-ui.tsx +51 -3
  275. package/src/plugins/impl/data-frames/__tests__/schema.test.ts +30 -0
  276. package/src/plugins/impl/data-frames/schema.ts +4 -1
  277. package/src/plugins/impl/image-comparison/ImageComparisonComponent.tsx +20 -1
  278. package/src/plugins/impl/image-comparison/__tests__/ImageComparisonComponent.test.tsx +58 -0
  279. package/src/stories/theme.stories.tsx +71 -1
  280. package/dist/assets/ImageComparisonComponent-COvcNAUu.js +0 -1
  281. package/dist/assets/add-cell-with-ai-BU5EDuHt.js +0 -107
  282. package/dist/assets/ai-model-dropdown-CLCa-gsg.js +0 -5
  283. package/dist/assets/cells-BVzo2MHf.css +0 -2
  284. package/dist/assets/chat-display-7CzBSZRm.js +0 -1
  285. package/dist/assets/chat-panel-Dd5WiR1a.js +0 -4
  286. package/dist/assets/chat-ui-CYqqqoSC.js +0 -4
  287. package/dist/assets/command-palette-BBlSC8AZ.js +0 -1
  288. package/dist/assets/components-K_Mi0vdC.js +0 -1
  289. package/dist/assets/config-BJVx3eBW.js +0 -1
  290. package/dist/assets/config-DAL-jiRM.js +0 -1
  291. package/dist/assets/defaultLocale-DOVIEEPN.js +0 -1
  292. package/dist/assets/dist-B99-E4qp.js +0 -1
  293. package/dist/assets/dist-Bfm9U3vp.js +0 -1
  294. package/dist/assets/dist-BiwuSVkx.js +0 -1
  295. package/dist/assets/dist-BrZjJXDA.js +0 -1
  296. package/dist/assets/dist-CaZf6AoY.js +0 -1
  297. package/dist/assets/dist-Cgoy5550.js +0 -1
  298. package/dist/assets/dist-Csf5i4KY.js +0 -1
  299. package/dist/assets/dist-D6Iobl3P.js +0 -1
  300. package/dist/assets/dist-D6WgndTj.js +0 -1
  301. package/dist/assets/dist-DcXy-1eJ.js +0 -1
  302. package/dist/assets/dist-DsfPGbgk.js +0 -1
  303. package/dist/assets/dist-DtbtRAqz.js +0 -1
  304. package/dist/assets/dist-I3qeQWuP.js +0 -1
  305. package/dist/assets/dist-aeKlrIMv.js +0 -1
  306. package/dist/assets/dist-gDi2I7IJ.js +0 -1
  307. package/dist/assets/dist-miDa4-cz.js +0 -1
  308. package/dist/assets/dist-nDEBxpL6.js +0 -1
  309. package/dist/assets/dist-oUL4XFdB.js +0 -1
  310. package/dist/assets/file-explorer-panel-4h-BKKaM.js +0 -26
  311. package/dist/assets/house-DS0r7dNZ.js +0 -1
  312. package/dist/assets/index-BXlfeAku.js +0 -38
  313. package/dist/assets/index-DYycoCjG.css +0 -2
  314. package/dist/assets/process-output-Cm_vuKBw.js +0 -1
  315. package/dist/assets/react-vega-BLc5RhC7.js +0 -188
  316. package/dist/assets/scratchpad-panel-DJkO5OrI.js +0 -1
  317. package/dist/assets/state-Brswj8bD.js +0 -1
  318. package/dist/assets/switch-CONP-vq2.js +0 -1
  319. package/dist/assets/tooltip-B7U4xkIN.js +0 -61
  320. package/dist/assets/useNotebookActions-BHaEvPEM.js +0 -12
  321. package/dist/assets/useRunCells-kmbsc2R5.js +0 -1
  322. package/dist/assets/utils-BWBBpCkT.js +0 -1
  323. package/dist/assets/vega-loader.browser-Dun7Qe4B.js +0 -6
  324. /package/dist/assets/{bundle.esm-D0TWQ6iT.js → bundle.esm-CvMga-X6.js} +0 -0
  325. /package/dist/assets/{icons-CIPE8AIY.js → icons-NFYKylj6.js} +0 -0
  326. /package/dist/assets/{useDeepCompareMemoize-CSMoa-ga.js → useDeepCompareMemoize-BLRnan2Y.js} +0 -0
@@ -17,6 +17,7 @@ import {
17
17
  CoreWeaveStorageSchema,
18
18
  GCSStorageSchema,
19
19
  GoogleDriveStorageSchema,
20
+ HuggingfaceStorageSchema,
20
21
  S3StorageSchema,
21
22
  type StorageConnection,
22
23
  } from "./schemas";
@@ -79,6 +80,15 @@ const STORAGE_PROVIDERS = [
79
80
  preferred: "fsspec",
80
81
  },
81
82
  },
83
+ {
84
+ name: "Hugging Face Hub",
85
+ schema: HuggingfaceStorageSchema,
86
+ protocol: "hf",
87
+ storageLibraries: {
88
+ libraries: ["huggingface_hub"],
89
+ preferred: "huggingface_hub",
90
+ },
91
+ },
82
92
  ] satisfies StorageProviderSchema[];
83
93
 
84
94
  const StorageProviderSelector: React.FC<{
@@ -10,7 +10,7 @@ import {
10
10
  StorageConnectionSchema,
11
11
  } from "./schemas";
12
12
 
13
- export type StorageLibrary = "obstore" | "fsspec";
13
+ export type StorageLibrary = "obstore" | "fsspec" | "huggingface_hub";
14
14
 
15
15
  export interface StorageCodeOptions {
16
16
  library: StorageLibrary;
@@ -20,6 +20,7 @@ export interface StorageCodeOptions {
20
20
  export const StorageLibraryDisplayNames: Record<StorageLibrary, string> = {
21
21
  obstore: "obstore",
22
22
  fsspec: "fsspec",
23
+ huggingface_hub: "huggingface_hub",
23
24
  };
24
25
 
25
26
  class SecretContainer {
@@ -245,6 +246,20 @@ function generateGDriveCode(
245
246
  return { imports, code };
246
247
  }
247
248
 
249
+ function generateHuggingfaceCode(
250
+ connection: Extract<StorageConnection, { type: "huggingface" }>,
251
+ secrets: SecretContainer,
252
+ ): { imports: Set<string>; code: string } {
253
+ const imports = new Set(["from huggingface_hub import HfApi"]);
254
+
255
+ if (!connection.token) {
256
+ return { imports, code: "hf = HfApi()" };
257
+ }
258
+
259
+ const token = secrets.print("token", connection.token);
260
+ return { imports, code: `hf = HfApi(token=${token})` };
261
+ }
262
+
248
263
  export function generateStorageCode(
249
264
  connection: StorageConnection,
250
265
  options: StorageCodeOptions,
@@ -273,6 +288,9 @@ export function generateStorageCode(
273
288
  isEmbedded: options.isEmbedded,
274
289
  });
275
290
  break;
291
+ case "huggingface":
292
+ result = generateHuggingfaceCode(connection, secrets);
293
+ break;
276
294
  default:
277
295
  assertNever(connection);
278
296
  }
@@ -220,12 +220,31 @@ export const GoogleDriveStorageSchema = z
220
220
  })
221
221
  .describe(FieldOptions.of({ direction: "two-columns" }));
222
222
 
223
+ export const HuggingfaceStorageSchema = z
224
+ .object({
225
+ type: z.literal("huggingface"),
226
+ token: z
227
+ .string()
228
+ .optional()
229
+ .describe(
230
+ FieldOptions.of({
231
+ label: "Access Token",
232
+ description:
233
+ "Leave empty to use the HF_TOKEN environment variable or cached login",
234
+ inputType: "password",
235
+ optionRegex: "(hf.?token|hugging.?face.?token|hub.?token)",
236
+ }),
237
+ ),
238
+ })
239
+ .describe(FieldOptions.of({ direction: "two-columns" }));
240
+
223
241
  export const StorageConnectionSchema = z.discriminatedUnion("type", [
224
242
  S3StorageSchema,
225
243
  GCSStorageSchema,
226
244
  AzureStorageSchema,
227
245
  CoreWeaveStorageSchema,
228
246
  GoogleDriveStorageSchema,
247
+ HuggingfaceStorageSchema,
229
248
  ]);
230
249
 
231
250
  export type StorageConnection = z.infer<typeof StorageConnectionSchema>;
@@ -44,6 +44,94 @@ describe("read-file snippet", () => {
44
44
  `);
45
45
  });
46
46
 
47
+ it("huggingface backend (dataset)", () => {
48
+ expect(
49
+ readSnippet.getCode(
50
+ makeCtx({
51
+ backendType: "huggingface",
52
+ entry: {
53
+ path: "datasets/scikit-learn/Fish/Fish.csv",
54
+ kind: "file",
55
+ size: 100,
56
+ lastModified: null,
57
+ },
58
+ }),
59
+ ),
60
+ ).toMatchInlineSnapshot(`
61
+ "from huggingface_hub import hf_hub_download
62
+
63
+ local_path = hf_hub_download(
64
+ repo_id="scikit-learn/Fish",
65
+ filename="Fish.csv",
66
+ repo_type="dataset",
67
+ )
68
+
69
+ with open(local_path, "rb") as f:
70
+ _data = f.read()
71
+ _data"
72
+ `);
73
+ });
74
+
75
+ it("huggingface backend (model)", () => {
76
+ expect(
77
+ readSnippet.getCode(
78
+ makeCtx({
79
+ backendType: "huggingface",
80
+ entry: {
81
+ path: "google-bert/bert-base-uncased/config.json",
82
+ kind: "file",
83
+ size: 100,
84
+ lastModified: null,
85
+ },
86
+ }),
87
+ ),
88
+ ).toMatchInlineSnapshot(`
89
+ "from huggingface_hub import hf_hub_download
90
+
91
+ local_path = hf_hub_download(
92
+ repo_id="google-bert/bert-base-uncased",
93
+ filename="config.json",
94
+ repo_type="model",
95
+ )
96
+
97
+ with open(local_path, "rb") as f:
98
+ _data = f.read()
99
+ _data"
100
+ `);
101
+ });
102
+
103
+ it("huggingface backend returns null for unparsable paths", () => {
104
+ expect(
105
+ readSnippet.getCode(
106
+ makeCtx({
107
+ backendType: "huggingface",
108
+ entry: {
109
+ path: "buckets/my-bucket/file.csv",
110
+ kind: "file",
111
+ size: 100,
112
+ lastModified: null,
113
+ },
114
+ }),
115
+ ),
116
+ ).toBeNull();
117
+ });
118
+
119
+ it("huggingface backend does not misparse a dataset/space root as a model repo", () => {
120
+ // These paths are missing a filename segment, so they aren't valid
121
+ // dataset/space repo files. They must not fall through and be treated
122
+ // as the model repo "datasets/scikit-learn" or "spaces/gradio".
123
+ for (const path of ["datasets/scikit-learn/Fish", "spaces/gradio/demo"]) {
124
+ expect(
125
+ readSnippet.getCode(
126
+ makeCtx({
127
+ backendType: "huggingface",
128
+ entry: { path, kind: "file", size: 100, lastModified: null },
129
+ }),
130
+ ),
131
+ ).toBeNull();
132
+ }
133
+ });
134
+
47
135
  it("returns null for directories", () => {
48
136
  expect(
49
137
  readSnippet.getCode(
@@ -9,6 +9,7 @@ import CoreweaveDarkIcon from "@marimo-team/llm-info/icons/coreweave-dark.svg?in
9
9
  import { DatabaseZapIcon, GlobeIcon, HardDriveIcon } from "lucide-react";
10
10
  import GoogleCloudIcon from "@/components/databases/icons/google-cloud-storage.svg?inline";
11
11
  import GoogleDriveIcon from "@/components/databases/icons/google-drive.svg?inline";
12
+ import HuggingfaceIcon from "@/components/databases/icons/huggingface.svg?inline";
12
13
  import { GitHubIcon } from "@/components/icons/github";
13
14
  import type { KnownStorageProtocol } from "@/core/storage/types";
14
15
  import { useTheme } from "@/theme/useTheme";
@@ -28,6 +29,7 @@ const PROTOCOL_ICONS: Record<KnownStorageProtocol, IconEntry> = {
28
29
  file: HardDriveIcon,
29
30
  "in-memory": DatabaseZapIcon,
30
31
  gdrive: { src: GoogleDriveIcon },
32
+ hf: { src: HuggingfaceIcon },
31
33
  github: GitHubIcon,
32
34
  };
33
35
 
@@ -27,6 +27,50 @@ function escapeForPythonString(value: string): string {
27
27
  return JSON.stringify(value).slice(1, -1);
28
28
  }
29
29
 
30
+ interface ParsedHfRepoPath {
31
+ repoType: "model" | "dataset" | "space";
32
+ repoId: string;
33
+ filename: string;
34
+ }
35
+
36
+ // Namespaced prefixes are reserved: they can never be the first segment of a
37
+ // model repo id, so a path like "datasets/org" that is missing a filename
38
+ // must not fall through and be misparsed as the model repo "datasets/org".
39
+ const RESERVED_PREFIXES = new Set(["datasets", "spaces", "buckets"]);
40
+
41
+ function parseHfRepoPath(path: string): ParsedHfRepoPath | null {
42
+ const parts = path.split("/").filter(Boolean);
43
+ if (parts[0] === "datasets" && parts.length >= 4) {
44
+ return {
45
+ repoType: "dataset",
46
+ repoId: `${parts[1]}/${parts[2]}`,
47
+ filename: parts.slice(3).join("/"),
48
+ };
49
+ }
50
+ if (parts[0] === "spaces" && parts.length >= 4) {
51
+ return {
52
+ repoType: "space",
53
+ repoId: `${parts[1]}/${parts[2]}`,
54
+ filename: parts.slice(3).join("/"),
55
+ };
56
+ }
57
+ if (parts.length >= 3 && !RESERVED_PREFIXES.has(parts[0])) {
58
+ return {
59
+ repoType: "model",
60
+ repoId: `${parts[0]}/${parts[1]}`,
61
+ filename: parts.slice(2).join("/"),
62
+ };
63
+ }
64
+ return null;
65
+ }
66
+
67
+ function hfHubDownloadSnippet(parsed: ParsedHfRepoPath): string {
68
+ const repoId = escapeForPythonString(parsed.repoId);
69
+ const filename = escapeForPythonString(parsed.filename);
70
+ const repoType = escapeForPythonString(parsed.repoType);
71
+ return `from huggingface_hub import hf_hub_download\n\nlocal_path = hf_hub_download(\n repo_id="${repoId}",\n filename="${filename}",\n repo_type="${repoType}",\n)`;
72
+ }
73
+
30
74
  export const STORAGE_SNIPPETS: StorageSnippet[] = [
31
75
  {
32
76
  id: "read-file",
@@ -37,6 +81,13 @@ export const STORAGE_SNIPPETS: StorageSnippet[] = [
37
81
  return null;
38
82
  }
39
83
  const path = escapeForPythonString(ctx.entry.path);
84
+ if (ctx.backendType === "huggingface") {
85
+ const parsed = parseHfRepoPath(ctx.entry.path);
86
+ if (!parsed) {
87
+ return null;
88
+ }
89
+ return `${hfHubDownloadSnippet(parsed)}\n\nwith open(local_path, "rb") as f:\n _data = f.read()\n_data`;
90
+ }
40
91
  if (ctx.backendType === "obstore") {
41
92
  return `_data = ${ctx.variableName}.get("${path}").bytes()\n_data`;
42
93
  }
@@ -52,6 +103,13 @@ export const STORAGE_SNIPPETS: StorageSnippet[] = [
52
103
  return null;
53
104
  }
54
105
  const path = escapeForPythonString(ctx.entry.path);
106
+ if (ctx.backendType === "huggingface") {
107
+ const parsed = parseHfRepoPath(ctx.entry.path);
108
+ if (!parsed) {
109
+ return null;
110
+ }
111
+ return `${hfHubDownloadSnippet(parsed)}\nlocal_path`;
112
+ }
55
113
  if (ctx.backendType === "obstore") {
56
114
  if (NOT_SIGNABLE_PROTOCOLS.has(ctx.protocol)) {
57
115
  return null;
@@ -0,0 +1,85 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
+ import { CellId } from "@/core/cells/ids";
5
+ import { runDuringPresentMode, viewStateAtom } from "@/core/mode";
6
+ import { store } from "@/core/state/jotai";
7
+
8
+ const requestAnimationFrameMock = vi.fn((callback: FrameRequestCallback) => {
9
+ callback(0);
10
+ return 0;
11
+ });
12
+
13
+ async function runAfterRender(fn: () => void | Promise<void>): Promise<void> {
14
+ const result = runDuringPresentMode(fn);
15
+ await vi.runAllTimersAsync();
16
+ return result;
17
+ }
18
+
19
+ describe("runDuringPresentMode", () => {
20
+ beforeEach(() => {
21
+ vi.useFakeTimers();
22
+ vi.stubGlobal("requestAnimationFrame", requestAnimationFrameMock);
23
+ requestAnimationFrameMock.mockClear();
24
+ });
25
+
26
+ afterEach(() => {
27
+ vi.useRealTimers();
28
+ vi.unstubAllGlobals();
29
+ });
30
+
31
+ it("runs in present mode and restores the captured view state", async () => {
32
+ const state = { mode: "edit" as const, cellAnchor: CellId.create() };
33
+ store.set(viewStateAtom, state);
34
+
35
+ await runAfterRender(() => {
36
+ expect(store.get(viewStateAtom)).toEqual({
37
+ ...state,
38
+ mode: "present",
39
+ });
40
+ });
41
+
42
+ expect(store.get(viewStateAtom)).toEqual(state);
43
+ expect(requestAnimationFrameMock).toHaveBeenCalledTimes(2);
44
+ });
45
+
46
+ it("restores the captured view state when the callback rejects", async () => {
47
+ const state = { mode: "edit" as const, cellAnchor: CellId.create() };
48
+ const error = new Error("capture failed");
49
+ store.set(viewStateAtom, state);
50
+
51
+ const result = runDuringPresentMode(() => Promise.reject(error));
52
+ const rejection = expect(result).rejects.toBe(error);
53
+ await vi.runAllTimersAsync();
54
+ await rejection;
55
+
56
+ expect(store.get(viewStateAtom)).toEqual(state);
57
+ });
58
+
59
+ it("runs directly when already in present mode", async () => {
60
+ const state = { mode: "present" as const, cellAnchor: CellId.create() };
61
+ store.set(viewStateAtom, state);
62
+
63
+ await runDuringPresentMode(() => {
64
+ expect(store.get(viewStateAtom)).toEqual(state);
65
+ });
66
+
67
+ expect(store.get(viewStateAtom)).toEqual(state);
68
+ expect(requestAnimationFrameMock).not.toHaveBeenCalled();
69
+ });
70
+
71
+ it.each(["read", "home", "gallery"] as const)(
72
+ "runs directly without changing %s mode",
73
+ async (mode) => {
74
+ const state = { mode, cellAnchor: CellId.create() };
75
+ store.set(viewStateAtom, state);
76
+
77
+ await runDuringPresentMode(() => {
78
+ expect(store.get(viewStateAtom)).toEqual(state);
79
+ });
80
+
81
+ expect(store.get(viewStateAtom)).toEqual(state);
82
+ expect(requestAnimationFrameMock).not.toHaveBeenCalled();
83
+ },
84
+ );
85
+ });
@@ -0,0 +1,96 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { act, renderHook } from "@testing-library/react";
4
+ import { createStore, Provider } from "jotai";
5
+ import type { ReactNode } from "react";
6
+ import { describe, expect, it, vi } from "vitest";
7
+ import { userConfigAtom } from "@/core/config/config";
8
+ import { defaultUserConfig } from "@/core/config/config-schema";
9
+ import { requestClientAtom } from "@/core/network/requests";
10
+ import type { EditRequests, RunRequests } from "@/core/network/types";
11
+ import { AiModelId } from "../ids/ids";
12
+ import { useAIConfigActions } from "../config";
13
+
14
+ describe("useAIConfigActions", () => {
15
+ it("persists a model change without overwriting newer AI config", async () => {
16
+ const store = createStore();
17
+ const saveUserConfig = vi.fn().mockResolvedValue(null);
18
+ store.set(userConfigAtom, defaultUserConfig());
19
+ store.set(requestClientAtom, {
20
+ saveUserConfig,
21
+ } as unknown as EditRequests & RunRequests);
22
+
23
+ const wrapper = ({ children }: { children: ReactNode }) => (
24
+ <Provider store={store}>{children}</Provider>
25
+ );
26
+ const { result } = renderHook(() => useAIConfigActions(), { wrapper });
27
+
28
+ store.set(userConfigAtom, (config) => ({
29
+ ...config,
30
+ ai: {
31
+ ...config.ai,
32
+ mode: "agent",
33
+ },
34
+ }));
35
+
36
+ await act(async () => {
37
+ await result.current.saveModelChange(
38
+ AiModelId.parse("openai/gpt-4o").id,
39
+ "chat",
40
+ );
41
+ });
42
+
43
+ expect(saveUserConfig).toHaveBeenCalledWith({
44
+ config: { ai: { models: { chat_model: "openai/gpt-4o" } } },
45
+ });
46
+ expect(store.get(userConfigAtom).ai).toMatchObject({
47
+ mode: "agent",
48
+ models: { chat_model: "openai/gpt-4o" },
49
+ });
50
+ });
51
+
52
+ it("preserves concurrent AI changes when saves resolve out of order", async () => {
53
+ const store = createStore();
54
+ const resolvers: Array<() => void> = [];
55
+ const saveUserConfig = vi.fn(
56
+ () =>
57
+ new Promise<null>((resolve) => {
58
+ resolvers.push(() => resolve(null));
59
+ }),
60
+ );
61
+ store.set(userConfigAtom, defaultUserConfig());
62
+ store.set(requestClientAtom, {
63
+ saveUserConfig,
64
+ } as unknown as EditRequests & RunRequests);
65
+
66
+ const wrapper = ({ children }: { children: ReactNode }) => (
67
+ <Provider store={store}>{children}</Provider>
68
+ );
69
+ const { result } = renderHook(() => useAIConfigActions(), { wrapper });
70
+
71
+ const modelChange = result.current.saveModelChange(
72
+ AiModelId.parse("openai/gpt-4o").id,
73
+ "chat",
74
+ );
75
+ const modeChange = result.current.saveModeChange("agent");
76
+
77
+ expect(saveUserConfig).toHaveBeenNthCalledWith(1, {
78
+ config: { ai: { models: { chat_model: "openai/gpt-4o" } } },
79
+ });
80
+ expect(saveUserConfig).toHaveBeenNthCalledWith(2, {
81
+ config: { ai: { mode: "agent" } },
82
+ });
83
+
84
+ await act(async () => {
85
+ resolvers[1]();
86
+ await modeChange;
87
+ resolvers[0]();
88
+ await modelChange;
89
+ });
90
+
91
+ expect(store.get(userConfigAtom).ai).toMatchObject({
92
+ mode: "agent",
93
+ models: { chat_model: "openai/gpt-4o" },
94
+ });
95
+ });
96
+ });
@@ -1,7 +1,8 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import type { Role } from "@marimo-team/llm-info";
4
- import { useAtom } from "jotai";
4
+ import { useSetAtom } from "jotai";
5
+ import { merge } from "lodash-es";
5
6
  import type { QualifiedModelId } from "@/core/ai/ids/ids";
6
7
  import { userConfigAtom } from "@/core/config/config";
7
8
  import type {
@@ -14,6 +15,11 @@ import { useRequestClient } from "@/core/network/requests";
14
15
  // Extract only the supported roles from the Role type
15
16
  export type SupportedRole = Extract<Role, "chat" | "autocomplete" | "edit">;
16
17
 
18
+ interface AiConfigPatch {
19
+ mode?: CopilotMode;
20
+ models?: Partial<NonNullable<NonNullable<UserConfig["ai"]>["models"]>>;
21
+ }
22
+
17
23
  const getModelKeyForRole = (forRole: SupportedRole): AIModelKey | null => {
18
24
  switch (forRole) {
19
25
  case "chat":
@@ -26,15 +32,16 @@ const getModelKeyForRole = (forRole: SupportedRole): AIModelKey | null => {
26
32
  };
27
33
 
28
34
  /**
29
- * Hook for saving model and mode changes.
35
+ * Hook for saving AI config changes
30
36
  */
31
- export const useModelChange = () => {
32
- const [userConfig, setUserConfig] = useAtom(userConfigAtom);
37
+ export const useAIConfigActions = () => {
38
+ const setUserConfig = useSetAtom(userConfigAtom);
33
39
  const { saveUserConfig } = useRequestClient();
34
40
 
35
- const saveConfig = async (newConfig: Partial<UserConfig>) => {
41
+ const saveConfig = async (aiConfig: AiConfigPatch) => {
42
+ const newConfig = { ai: aiConfig };
36
43
  await saveUserConfig({ config: newConfig }).then(() => {
37
- setUserConfig((prev) => ({ ...prev, ...newConfig }));
44
+ setUserConfig((prev) => merge({}, prev, newConfig));
38
45
  });
39
46
  };
40
47
 
@@ -48,28 +55,15 @@ export const useModelChange = () => {
48
55
  return;
49
56
  }
50
57
 
51
- const newConfig: Partial<UserConfig> = {
52
- ai: {
53
- ...userConfig.ai,
54
- models: {
55
- custom_models: userConfig.ai?.models?.custom_models ?? [],
56
- displayed_models: userConfig.ai?.models?.displayed_models ?? [],
57
- ...userConfig.ai?.models,
58
- [modelKey]: model,
59
- },
60
- },
58
+ const newConfig: AiConfigPatch = {
59
+ models: { [modelKey]: model },
61
60
  };
62
61
 
63
62
  await saveConfig(newConfig);
64
63
  };
65
64
 
66
65
  const saveModeChange = async (newMode: CopilotMode) => {
67
- const newConfig: Partial<UserConfig> = {
68
- ai: {
69
- ...userConfig.ai,
70
- mode: newMode,
71
- },
72
- };
66
+ const newConfig: AiConfigPatch = { mode: newMode };
73
67
 
74
68
  await saveConfig(newConfig);
75
69
  };
@@ -0,0 +1,7 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ /**
4
+ * Throttle wait (ms) for AI SDK React hooks (`useChat` / `useCompletion`).
5
+ * Limits UI re-renders while streaming tokens.
6
+ */
7
+ export const AI_SDK_UI_THROTTLE_MS = 100;
@@ -176,14 +176,17 @@ export function useStagedCells(store: JotaiStore) {
176
176
  case "message-metadata":
177
177
  case "tool-input-available":
178
178
  case "tool-output-available":
179
+ case "tool-approval-response":
179
180
  case "reasoning-start":
180
181
  case "reasoning-delta":
181
182
  case "reasoning-end":
182
183
  case "file":
184
+ case "reasoning-file":
183
185
  case "source-document":
184
186
  case "source-url":
185
187
  case "tool-input-start":
186
188
  case "tool-input-delta":
189
+ case "custom":
187
190
  Logger.debug(chunk.type, { chunk });
188
191
  break;
189
192
  default:
@@ -8,6 +8,7 @@ import { uniqueBy } from "@/utils/arrays";
8
8
  import { adaptForLocalStorage, jotaiJsonStorage } from "@/utils/storage/jotai";
9
9
  import type { TypedString } from "@/utils/typed";
10
10
  import type { CellId } from "../cells/ids";
11
+ import type { ChatOptions } from "../network/types";
11
12
 
12
13
  const KEY = "marimo:ai:chatState:v5";
13
14
 
@@ -32,6 +33,15 @@ export interface PendingAiPrompt {
32
33
 
33
34
  export const pendingAiPromptAtom = atom<PendingAiPrompt | null>(null);
34
35
 
36
+ const CHAT_OPTIONS_KEY = "marimo:ai:chatOptions";
37
+ export const chatOptionsAtom = atomWithStorage<Required<ChatOptions>>(
38
+ CHAT_OPTIONS_KEY,
39
+ {
40
+ webSearch: false,
41
+ },
42
+ jotaiJsonStorage,
43
+ );
44
+
35
45
  const INCLUDE_OTHER_CELLS_KEY = "marimo:ai:includeOtherCells";
36
46
  export const includeOtherCellsAtom = atomWithStorage<boolean>(
37
47
  INCLUDE_OTHER_CELLS_KEY,
@@ -68,7 +68,7 @@ function getOpts() {
68
68
  diagnosticsConfig: {},
69
69
  hotkeys: new OverridingHotkeyProvider({}),
70
70
  theme: "light",
71
- displayConfig: { reference_highlighting: false },
71
+ displayConfig: { reference_highlighting: false, code_lens: true },
72
72
  inlineAiTooltip: false,
73
73
  } as const;
74
74
  }
@@ -82,7 +82,7 @@ export interface CodeMirrorSetupOpts {
82
82
  hotkeys: HotkeyProvider;
83
83
  lspConfig: LSPConfig;
84
84
  diagnosticsConfig: DiagnosticsConfig;
85
- displayConfig: Pick<DisplayConfig, "reference_highlighting">;
85
+ displayConfig: Pick<DisplayConfig, "reference_highlighting" | "code_lens">;
86
86
  inlineAiTooltip: boolean;
87
87
  /**
88
88
  * CSS selector for the element that CodeMirror tooltips (completions, hover,
@@ -200,7 +200,7 @@ export const setupCodeMirror = (opts: CodeMirrorSetupOpts): Extension[] => {
200
200
  displayConfig.reference_highlighting ?? true,
201
201
  ),
202
202
  // Inline icons linking datasources/buckets/caches to their panels
203
- codeLensBundle(cellId),
203
+ codeLensBundle(cellId, displayConfig.code_lens ?? true),
204
204
  ];
205
205
  };
206
206
 
@@ -103,7 +103,7 @@ describe("codeLensBundle", () => {
103
103
 
104
104
  beforeEach(() => {
105
105
  vi.useFakeTimers();
106
- mockFlags({ editor_code_lens: true, cache_panel: true });
106
+ mockFlags({ cache_panel: true });
107
107
  seedStore({});
108
108
  });
109
109
 
@@ -141,9 +141,8 @@ describe("codeLensBundle", () => {
141
141
  return [...v.dom.querySelectorAll<HTMLElement>(".mo-code-lens")];
142
142
  }
143
143
 
144
- it("returns an empty extension when the flag is off", () => {
145
- mockFlags({ editor_code_lens: false });
146
- expect(codeLensBundle(cellId("cell1"))).toEqual([]);
144
+ it("returns an empty extension when disabled via `display.code_lens`", () => {
145
+ expect(codeLensBundle(cellId("cell1"), false)).toEqual([]);
147
146
  });
148
147
 
149
148
  it("renders a lens at a dataframe declaration", async () => {
@@ -293,7 +292,7 @@ describe("codeLensBundle", () => {
293
292
  );
294
293
 
295
294
  it("does not render cache lenses when the cache panel is disabled", async () => {
296
- mockFlags({ editor_code_lens: true, cache_panel: false });
295
+ mockFlags({ cache_panel: false });
297
296
  const v = await mount("@mo.cache\ndef f():\n return 1");
298
297
 
299
298
  expect(lenses(v)).toHaveLength(0);
@@ -281,10 +281,10 @@ const codeLensTheme = EditorView.baseTheme({
281
281
  /**
282
282
  * Inline icons linking datasource/bucket variables and `mo.cache` /
283
283
  * `mo.persistent_cache` calls to their panels.
284
- * Gated behind the `editor_code_lens` feature flag.
284
+ * Configurable via the `display.code_lens` user config (on by default).
285
285
  */
286
- export function codeLensBundle(cellId: CellId): Extension {
287
- if (!getFeatureFlag("editor_code_lens")) {
286
+ export function codeLensBundle(cellId: CellId, enabled = true): Extension {
287
+ if (!enabled) {
288
288
  return [];
289
289
  }
290
290
  return [