@marimo-team/frontend 0.23.17-dev4 → 0.23.17-dev40

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 (315) 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-CWfu9qbN.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-B9QSRg6T.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-GZi9Icll.js +107 -0
  13. package/dist/assets/{add-connection-dialog-BZaJAQCM.js → add-connection-dialog-D2WiF1f8.js} +31 -31
  14. package/dist/assets/{agent-panel-CRxiqCgP.js → agent-panel-BWIkm6ln.js} +6 -6
  15. package/dist/assets/ai-model-dropdown-BmcXWjub.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-QHaORuxs.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-Cwor7oUx.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-CH-dFAC3.js +1 -0
  28. package/dist/assets/chat-panel-DLgorYjE.js +4 -0
  29. package/dist/assets/chat-ui-DDkwFM3p.js +4 -0
  30. package/dist/assets/{column-preview-CnqeJb-x.js → column-preview-Dw1ASLnm.js} +1 -1
  31. package/dist/assets/{command-Dqxie4gq.js → command-CPFHYoYk.js} +1 -1
  32. package/dist/assets/command-palette--Mo9OaH2.js +1 -0
  33. package/dist/assets/{common-D_beLbpm.js → common-aHaDUD0-.js} +1 -1
  34. package/dist/assets/{components-CxEGXaTB.js → components-BQ3c1B_F.js} +1 -1
  35. package/dist/assets/components-Bm1d4vm9.js +1 -0
  36. package/dist/assets/config-Bm5B9wGP.js +1 -0
  37. package/dist/assets/{config-BJVx3eBW.js → config-Cbkt6Rb1.js} +1 -1
  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-DfQvKiJ1.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-gbgUGtYS.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-DhZPSJ6P.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-Upmm4kG5.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-BulU96v_.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-BsW7zr4t.js → form-BIUQB1NI.js} +1 -1
  111. package/dist/assets/{form-oz4Z280Z.js → form-D-Bo8F9Q.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-Dvm2dFdU.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-BPwg3EAk.js +38 -0
  121. package/dist/assets/index-Dttsymhw.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-D7Yzn4XY.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-IN-yS3e2.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-C5PIEySE.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-BPaqYFXU.js} +2 -2
  150. package/dist/assets/{run-page-DDty5GvQ.js → run-page-kWVbQ6KV.js} +1 -1
  151. package/dist/assets/{runs-s9t2f_Tg.js → runs-C6avq6Gi.js} +1 -1
  152. package/dist/assets/scratchpad-panel-l1iGto9s.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-CBmLYzFL.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-DhhF_mJP.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-Brswj8bD.js → state-COqqdiUM.js} +1 -1
  165. package/dist/assets/{state-hlEqpZfH.js → state-D9xpDCxa.js} +2 -2
  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-CrFbi8en.js} +1 -1
  179. package/dist/assets/{useDeleteCell-Cy23OP4e.js → useDeleteCell-DH0weyin.js} +1 -1
  180. package/dist/assets/{useDependencyPanelTab-DNfjp_-X.js → useDependencyPanelTab-OeFV2bye.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-CylmZilh.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 +6 -6
  199. package/src/__mocks__/requests.ts +5 -0
  200. package/src/components/app-config/user-config-form.tsx +26 -0
  201. package/src/components/chat/__tests__/chat-abort.test.ts +51 -0
  202. package/src/components/chat/chat-abort.ts +44 -0
  203. package/src/components/chat/chat-display.tsx +17 -0
  204. package/src/components/chat/chat-panel.tsx +85 -37
  205. package/src/components/chat/chat-utils.ts +1 -1
  206. package/src/components/data-table/charts/__tests__/schemas.test.ts +35 -0
  207. package/src/components/data-table/charts/schemas.ts +5 -2
  208. package/src/components/databases/icons/huggingface.svg +8 -0
  209. package/src/components/datasets/__tests__/icons.test.ts +21 -0
  210. package/src/components/datasets/icons.tsx +10 -0
  211. package/src/components/datasources/__tests__/components.test.tsx +29 -0
  212. package/src/components/datasources/components.tsx +8 -3
  213. package/src/components/editor/actions/export-dialog/__tests__/export-dialog.test.tsx +35 -0
  214. package/src/components/editor/actions/export-dialog/__tests__/state.test.ts +61 -0
  215. package/src/components/editor/actions/export-dialog/export-command.ts +4 -9
  216. package/src/components/editor/actions/export-dialog/export-dialog.tsx +27 -23
  217. package/src/components/editor/actions/export-dialog/format-notice.tsx +28 -0
  218. package/src/components/editor/actions/export-dialog/state.ts +23 -5
  219. package/src/components/editor/actions/export-dialog/use-export-dialog.ts +7 -15
  220. package/src/components/editor/actions/useConfigActions.tsx +24 -0
  221. package/src/components/editor/ai/add-cell-with-ai.tsx +2 -2
  222. package/src/components/editor/ai/ai-completion-editor.tsx +2 -2
  223. package/src/components/editor/chrome/panels/outline/__tests__/useActiveOutline.test.ts +26 -0
  224. package/src/components/editor/code/__tests__/readonly-python-code.test.tsx +114 -2
  225. package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +14 -0
  226. package/src/components/editor/connections/storage/__tests__/as-code.test.ts +20 -0
  227. package/src/components/editor/connections/storage/add-storage-form.tsx +10 -0
  228. package/src/components/editor/connections/storage/as-code.ts +19 -1
  229. package/src/components/editor/connections/storage/schemas.ts +19 -0
  230. package/src/components/storage/__tests__/storage-snippets.test.ts +88 -0
  231. package/src/components/storage/components.tsx +2 -0
  232. package/src/components/storage/storage-snippets.ts +58 -0
  233. package/src/core/__tests__/mode.test.ts +85 -0
  234. package/src/core/ai/constants.ts +7 -0
  235. package/src/core/ai/staged-cells.ts +3 -0
  236. package/src/core/codemirror/__tests__/setup.test.ts +1 -1
  237. package/src/core/codemirror/cm.ts +2 -2
  238. package/src/core/codemirror/code-lens/__tests__/extension.test.ts +4 -5
  239. package/src/core/codemirror/code-lens/extension.ts +3 -3
  240. package/src/core/codemirror/find-replace/search-highlight.ts +8 -3
  241. package/src/core/codemirror/language/languages/sql/__tests__/renderers.test.tsx +44 -0
  242. package/src/core/codemirror/language/languages/sql/renderers.tsx +3 -3
  243. package/src/core/codemirror/theme/__tests__/light.test.ts +59 -123
  244. package/src/core/codemirror/theme/dark.ts +6 -5
  245. package/src/core/codemirror/theme/light.ts +6 -4
  246. package/src/core/config/__tests__/config-schema.test.ts +2 -0
  247. package/src/core/config/config-schema.ts +1 -0
  248. package/src/core/config/feature-flag.tsx +0 -2
  249. package/src/core/dom/outline.ts +25 -2
  250. package/src/core/hotkeys/hotkeys.ts +2 -0
  251. package/src/core/mode.ts +11 -9
  252. package/src/core/network/__tests__/api.test.ts +132 -17
  253. package/src/core/network/__tests__/export-filename.test.ts +43 -0
  254. package/src/core/network/__tests__/requests-lazy.test.ts +1 -0
  255. package/src/core/network/api.ts +47 -12
  256. package/src/core/network/export-filename.ts +37 -0
  257. package/src/core/network/markdown-export.ts +19 -0
  258. package/src/core/network/requests-network.ts +29 -5
  259. package/src/core/network/types.ts +3 -0
  260. package/src/core/storage/types.ts +1 -0
  261. package/src/css/app/codemirror.css +11 -7
  262. package/src/css/globals.css +8 -0
  263. package/src/plugins/impl/DataEditorPlugin.tsx +2 -9
  264. package/src/plugins/impl/__tests__/DataEditorPlugin.test.ts +18 -0
  265. package/src/plugins/impl/chat/chat-ui.tsx +51 -3
  266. package/src/plugins/impl/data-frames/__tests__/schema.test.ts +30 -0
  267. package/src/plugins/impl/data-frames/schema.ts +4 -1
  268. package/src/plugins/impl/image-comparison/ImageComparisonComponent.tsx +20 -1
  269. package/src/plugins/impl/image-comparison/__tests__/ImageComparisonComponent.test.tsx +58 -0
  270. package/src/stories/theme.stories.tsx +71 -1
  271. package/dist/assets/ImageComparisonComponent-COvcNAUu.js +0 -1
  272. package/dist/assets/add-cell-with-ai-BU5EDuHt.js +0 -107
  273. package/dist/assets/ai-model-dropdown-CLCa-gsg.js +0 -5
  274. package/dist/assets/cells-BVzo2MHf.css +0 -2
  275. package/dist/assets/chat-display-7CzBSZRm.js +0 -1
  276. package/dist/assets/chat-panel-Dd5WiR1a.js +0 -4
  277. package/dist/assets/chat-ui-CYqqqoSC.js +0 -4
  278. package/dist/assets/command-palette-BBlSC8AZ.js +0 -1
  279. package/dist/assets/components-K_Mi0vdC.js +0 -1
  280. package/dist/assets/config-DAL-jiRM.js +0 -1
  281. package/dist/assets/defaultLocale-DOVIEEPN.js +0 -1
  282. package/dist/assets/dist-B99-E4qp.js +0 -1
  283. package/dist/assets/dist-Bfm9U3vp.js +0 -1
  284. package/dist/assets/dist-BiwuSVkx.js +0 -1
  285. package/dist/assets/dist-BrZjJXDA.js +0 -1
  286. package/dist/assets/dist-CaZf6AoY.js +0 -1
  287. package/dist/assets/dist-Cgoy5550.js +0 -1
  288. package/dist/assets/dist-Csf5i4KY.js +0 -1
  289. package/dist/assets/dist-D6Iobl3P.js +0 -1
  290. package/dist/assets/dist-D6WgndTj.js +0 -1
  291. package/dist/assets/dist-DcXy-1eJ.js +0 -1
  292. package/dist/assets/dist-DsfPGbgk.js +0 -1
  293. package/dist/assets/dist-DtbtRAqz.js +0 -1
  294. package/dist/assets/dist-I3qeQWuP.js +0 -1
  295. package/dist/assets/dist-aeKlrIMv.js +0 -1
  296. package/dist/assets/dist-gDi2I7IJ.js +0 -1
  297. package/dist/assets/dist-miDa4-cz.js +0 -1
  298. package/dist/assets/dist-nDEBxpL6.js +0 -1
  299. package/dist/assets/dist-oUL4XFdB.js +0 -1
  300. package/dist/assets/file-explorer-panel-4h-BKKaM.js +0 -26
  301. package/dist/assets/house-DS0r7dNZ.js +0 -1
  302. package/dist/assets/index-BXlfeAku.js +0 -38
  303. package/dist/assets/index-DYycoCjG.css +0 -2
  304. package/dist/assets/process-output-Cm_vuKBw.js +0 -1
  305. package/dist/assets/react-vega-BLc5RhC7.js +0 -188
  306. package/dist/assets/scratchpad-panel-DJkO5OrI.js +0 -1
  307. package/dist/assets/switch-CONP-vq2.js +0 -1
  308. package/dist/assets/tooltip-B7U4xkIN.js +0 -61
  309. package/dist/assets/useNotebookActions-BHaEvPEM.js +0 -12
  310. package/dist/assets/useRunCells-kmbsc2R5.js +0 -1
  311. package/dist/assets/utils-BWBBpCkT.js +0 -1
  312. package/dist/assets/vega-loader.browser-Dun7Qe4B.js +0 -6
  313. /package/dist/assets/{bundle.esm-D0TWQ6iT.js → bundle.esm-CvMga-X6.js} +0 -0
  314. /package/dist/assets/{icons-CIPE8AIY.js → icons-NFYKylj6.js} +0 -0
  315. /package/dist/assets/{useDeepCompareMemoize-CSMoa-ga.js → useDeepCompareMemoize-BLRnan2Y.js} +0 -0
@@ -101,31 +101,35 @@ export const ExportDialog: React.FC<{
101
101
  })}
102
102
  </TabsList>
103
103
 
104
- <TabsContent
105
- value={format}
106
- className="mt-0 min-h-0 min-w-0 overflow-y-auto px-4 py-3"
107
- >
108
- <div className="mb-3">
109
- <h3 className="text-base font-semibold">{definition.label}</h3>
110
- <p className="mt-0.5 text-sm text-muted-foreground">
111
- {definition.description}
112
- </p>
113
- </div>
114
-
115
- <FormatNotice
116
- format={format}
117
- formatLabel={definition.label}
118
- status={status}
119
- />
104
+ {/* One panel per tab so each trigger's aria-controls resolves. */}
105
+ {formats.map(({ format: candidate }) => (
106
+ <TabsContent
107
+ key={candidate}
108
+ value={candidate}
109
+ className="mt-0 min-h-0 min-w-0 overflow-y-auto px-4 py-3"
110
+ >
111
+ <div className="mb-3">
112
+ <h3 className="text-base font-semibold">{definition.label}</h3>
113
+ <p className="mt-0.5 text-sm text-muted-foreground">
114
+ {definition.description}
115
+ </p>
116
+ </div>
120
117
 
121
- {usesBrowserPrint ? null : (
122
- <FormatOptions
123
- options={options}
124
- updateOptions={updateOptions}
125
- disabled={isExporting}
118
+ <FormatNotice
119
+ format={format}
120
+ formatLabel={definition.label}
121
+ status={status}
126
122
  />
127
- )}
128
- </TabsContent>
123
+
124
+ {usesBrowserPrint ? null : (
125
+ <FormatOptions
126
+ options={options}
127
+ updateOptions={updateOptions}
128
+ disabled={isExporting}
129
+ />
130
+ )}
131
+ </TabsContent>
132
+ ))}
129
133
  </Tabs>
130
134
 
131
135
  <footer className="min-w-0 border-t bg-muted/20 px-4 py-3">
@@ -10,8 +10,18 @@ import type {
10
10
  ExportBlockReason,
11
11
  ExportFormat,
12
12
  ExportFormatStatus,
13
+ ExportSetupRequirement,
13
14
  } from "./state";
14
15
 
16
+ const SETUP_REQUIREMENTS: Record<
17
+ ExportSetupRequirement["name"],
18
+ { description: string }
19
+ > = {
20
+ "playwright-chromium": {
21
+ description: "PDF export requires Playwright Chromium.",
22
+ },
23
+ };
24
+
15
25
  export function FormatStatusIcon({ status }: { status: ExportFormatStatus }) {
16
26
  if (status.available && !status.availabilityCheckFailed) {
17
27
  return null;
@@ -124,6 +134,24 @@ function ReasonNotice({
124
134
  where marimo is running to use this export.
125
135
  </Notice>
126
136
  );
137
+ case "missing-setup":
138
+ return (
139
+ <Notice>
140
+ <span className="block min-w-0">
141
+ {reason.requirements.map((requirement) => {
142
+ const details = SETUP_REQUIREMENTS[requirement.name];
143
+ return (
144
+ <span className="block" key={requirement.name}>
145
+ {details.description}
146
+ <code className="mt-1 block break-all font-mono">
147
+ {requirement.command}
148
+ </code>
149
+ </span>
150
+ );
151
+ })}
152
+ </span>
153
+ </Notice>
154
+ );
127
155
  case "wasm-runtime":
128
156
  return (
129
157
  <Notice>
@@ -15,6 +15,8 @@ export const EXPORT_FORMATS = [
15
15
  ] as const;
16
16
 
17
17
  export type ExportFormat = (typeof EXPORT_FORMATS)[number];
18
+ export type ExportSetupRequirement =
19
+ ExportAvailabilityResponse["formats"][number]["missingSetup"][number];
18
20
 
19
21
  export const MARKDOWN_FLAVORS = ["pymdown", "qmd", "mystmd", "mdx"] as const;
20
22
  export const IPYNB_SORT_MODES = ["topological", "top-down"] as const;
@@ -185,6 +187,7 @@ export type ExportBlockReason =
185
187
  | { type: "checking-requirements" }
186
188
  | { type: "notebook-must-be-named" }
187
189
  | { type: "missing-packages"; packages: string[] }
190
+ | { type: "missing-setup"; requirements: ExportSetupRequirement[] }
188
191
  | { type: "wasm-runtime" };
189
192
 
190
193
  export interface ExportFormatStatus {
@@ -308,12 +311,27 @@ export function getExportFormatStatus({
308
311
  return { available: true, availabilityCheckFailed: true };
309
312
  }
310
313
  if (!serverStatus.dependenciesAvailable) {
314
+ if (serverStatus.missingPackages.length > 0) {
315
+ return {
316
+ available: false,
317
+ reason: {
318
+ type: "missing-packages",
319
+ packages: serverStatus.missingPackages,
320
+ },
321
+ };
322
+ }
323
+ if (serverStatus.missingSetup.length > 0) {
324
+ return {
325
+ available: false,
326
+ reason: {
327
+ type: "missing-setup",
328
+ requirements: serverStatus.missingSetup,
329
+ },
330
+ };
331
+ }
311
332
  return {
312
- available: false,
313
- reason: {
314
- type: "missing-packages",
315
- packages: serverStatus.missingPackages,
316
- },
333
+ available: true,
334
+ availabilityCheckFailed: true,
317
335
  };
318
336
  }
319
337
 
@@ -1,13 +1,13 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import { useAtom, useAtomValue, useSetAtom } from "jotai";
3
+ import { useAtom } from "jotai";
4
4
  import { useEffect, useRef, useState } from "react";
5
5
  import { toast } from "@/components/ui/use-toast";
6
6
  import {
7
7
  updateCellOutputsWithScreenshots,
8
8
  useEnrichCellOutputs,
9
9
  } from "@/core/export/hooks";
10
- import { runDuringPresentMode, viewStateAtom } from "@/core/mode";
10
+ import { runDuringPresentMode } from "@/core/mode";
11
11
  import { useRequestClient } from "@/core/network/requests";
12
12
  import type { ExportAvailabilityResponse } from "@/core/network/types";
13
13
  import { useFilename } from "@/core/saving/filename";
@@ -21,6 +21,7 @@ import {
21
21
  withLoadingToast,
22
22
  } from "@/utils/download";
23
23
  import { Filenames } from "@/utils/filenames";
24
+ import { Logger } from "@/utils/Logger";
24
25
  import { Paths } from "@/utils/paths";
25
26
  import { getExportCommand } from "./export-command";
26
27
  import { exportNotebook } from "./export-notebook";
@@ -277,8 +278,6 @@ function useExportDialogAction({
277
278
  }) {
278
279
  const requests = useRequestClient();
279
280
  const takeScreenshots = useEnrichCellOutputs();
280
- const viewState = useAtomValue(viewStateAtom);
281
- const setViewState = useSetAtom(viewStateAtom);
282
281
  const [isExporting, setIsExporting] = useState(false);
283
282
  const dialogRef = useRef<HTMLDivElement>(null);
284
283
  const mountedRef = useRef(true);
@@ -302,15 +301,7 @@ function useExportDialogAction({
302
301
  const capturePNG = async () => {
303
302
  const capture = () =>
304
303
  captureCurrentAppView(dialogRef.current?.parentElement ?? null);
305
- if (viewState.mode !== "edit") {
306
- await capture();
307
- return;
308
- }
309
- try {
310
- await runDuringPresentMode(capture);
311
- } finally {
312
- setViewState(viewState);
313
- }
304
+ await runDuringPresentMode(capture);
314
305
  };
315
306
 
316
307
  const submit = async () => {
@@ -342,8 +333,9 @@ function useExportDialogAction({
342
333
  if (mountedRef.current) {
343
334
  onClose();
344
335
  }
345
- } catch {
346
- // Request and capture helpers report actionable errors to the user.
336
+ } catch (error) {
337
+ // Most helpers toast actionable errors, but not all (e.g. updateCellOutputs).
338
+ Logger.error("Export failed", error);
347
339
  } finally {
348
340
  if (mountedRef.current) {
349
341
  setIsExporting(false);
@@ -141,6 +141,30 @@ export function useConfigActions() {
141
141
  });
142
142
  },
143
143
  },
144
+ {
145
+ label: "Config > Disable code lens",
146
+ hidden: !(config.display.code_lens ?? true),
147
+ handle: () => {
148
+ handleUserConfig({
149
+ display: {
150
+ ...config.display,
151
+ code_lens: false,
152
+ },
153
+ });
154
+ },
155
+ },
156
+ {
157
+ label: "Config > Enable code lens",
158
+ hidden: config.display.code_lens ?? true,
159
+ handle: () => {
160
+ handleUserConfig({
161
+ display: {
162
+ ...config.display,
163
+ code_lens: true,
164
+ },
165
+ });
166
+ },
167
+ },
144
168
  {
145
169
  label: "Config > Set cell output area: above",
146
170
  hidden: config.display.cell_output === "above",
@@ -51,6 +51,7 @@ import {
51
51
  } from "@/components/ui/dropdown-menu";
52
52
  import { toast } from "@/components/ui/use-toast";
53
53
  import { AiModelId } from "@/core/ai/ids/ids";
54
+ import { AI_SDK_UI_THROTTLE_MS } from "@/core/ai/constants";
54
55
  import { stagedAICellsAtom, useStagedCells } from "@/core/ai/staged-cells";
55
56
  import type { ToolNotebookContext } from "@/core/ai/tools/base";
56
57
  import { useCellActions } from "@/core/cells/cells";
@@ -117,8 +118,7 @@ export const AddCellWithAI: React.FC<{
117
118
  };
118
119
 
119
120
  const { sendMessage, stop, status, addToolOutput } = useChat({
120
- // Throttle the messages and data updates to 100ms
121
- experimental_throttle: 100,
121
+ throttle: AI_SDK_UI_THROTTLE_MS,
122
122
  transport: new StreamingChunkTransport(
123
123
  {
124
124
  api: runtimeManager.getAiURL("completion").toString(),
@@ -27,6 +27,7 @@ import { Label } from "@/components/ui/label";
27
27
  import { Switch } from "@/components/ui/switch";
28
28
  import { Tooltip } from "@/components/ui/tooltip";
29
29
  import { toast } from "@/components/ui/use-toast";
30
+ import { AI_SDK_UI_THROTTLE_MS } from "@/core/ai/constants";
30
31
  import { stripWrappingBackticks } from "@/core/ai/strip-wrapping-backticks";
31
32
  import { type AiCompletionCell, includeOtherCellsAtom } from "@/core/ai/state";
32
33
  import type { CellId } from "@/core/cells/ids";
@@ -124,8 +125,7 @@ export const AiCompletionEditor: React.FC<Props> = ({
124
125
  api: runtimeManager.getAiURL("completion").toString(),
125
126
  headers: runtimeManager.headers(),
126
127
  initialInput: initialPrompt,
127
- // Throttle the messages and data updates to 100ms
128
- experimental_throttle: 100,
128
+ throttle: AI_SDK_UI_THROTTLE_MS,
129
129
  body: {
130
130
  ...(Object.keys(completionBody).length > 0
131
131
  ? completionBody
@@ -0,0 +1,26 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import { describe, expect, it } from "vitest";
3
+ import { parseOutline } from "@/core/dom/outline";
4
+ import { findOutlineElements } from "../useActiveOutline";
5
+
6
+ describe("findOutlineElements", () => {
7
+ it.each([
8
+ "Design from the portfolio",
9
+ 'Decorative Art of "Spanish California"',
10
+ `The painter's "Study"`,
11
+ ])("finds an id-less heading named %s", (name) => {
12
+ const html = `<h3>${name}</h3>`;
13
+ document.body.innerHTML = html;
14
+
15
+ const outline = parseOutline({
16
+ mimetype: "text/html",
17
+ timestamp: 0,
18
+ channel: "output",
19
+ data: html,
20
+ });
21
+
22
+ expect(
23
+ findOutlineElements(outline?.items ?? []).map(([element]) => element),
24
+ ).toEqual([document.querySelector("h3")]);
25
+ });
26
+ });
@@ -1,13 +1,61 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import { fireEvent, render, screen } from "@testing-library/react";
4
- import { beforeAll, describe, expect, it } from "vitest";
3
+ import { EditorView } from "@codemirror/view";
4
+ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
5
+ import {
6
+ afterAll,
7
+ beforeAll,
8
+ beforeEach,
9
+ describe,
10
+ expect,
11
+ it,
12
+ vi,
13
+ } from "vitest";
5
14
  import { SetupMocks } from "@/__mocks__/common";
6
15
  import { TooltipProvider } from "@/components/ui/tooltip";
7
16
  import { ReadonlyCode } from "../readonly-python-code";
8
17
 
18
+ const themeState = vi.hoisted(() => ({
19
+ value: "light" as "light" | "dark",
20
+ }));
21
+
22
+ let originalRangeGetClientRects: PropertyDescriptor | undefined;
23
+
24
+ vi.mock("@/theme/useTheme", async (importOriginal) => {
25
+ const actual = await importOriginal<typeof import("@/theme/useTheme")>();
26
+ return {
27
+ ...actual,
28
+ useTheme: () => ({ theme: themeState.value }),
29
+ };
30
+ });
31
+
9
32
  beforeAll(() => {
10
33
  SetupMocks.resizeObserver();
34
+ originalRangeGetClientRects = Object.getOwnPropertyDescriptor(
35
+ Range.prototype,
36
+ "getClientRects",
37
+ );
38
+ Object.defineProperty(Range.prototype, "getClientRects", {
39
+ configurable: true,
40
+ value: () => [],
41
+ });
42
+ });
43
+
44
+ afterAll(() => {
45
+ // Keep the shared JSDOM prototype unchanged for other test files.
46
+ if (originalRangeGetClientRects) {
47
+ Object.defineProperty(
48
+ Range.prototype,
49
+ "getClientRects",
50
+ originalRangeGetClientRects,
51
+ );
52
+ } else {
53
+ Reflect.deleteProperty(Range.prototype, "getClientRects");
54
+ }
55
+ });
56
+
57
+ beforeEach(() => {
58
+ themeState.value = "light";
11
59
  });
12
60
 
13
61
  /**
@@ -26,7 +74,71 @@ function isCollapsed(root: ParentNode) {
26
74
  return root.querySelector(".cm")?.classList.contains("opacity-20") ?? false;
27
75
  }
28
76
 
77
+ function getEditorIdentity(view: EditorView) {
78
+ return {
79
+ doc: view.state.doc,
80
+ dom: view.dom,
81
+ contentDOM: view.contentDOM,
82
+ scrollDOM: view.scrollDOM,
83
+ };
84
+ }
85
+
86
+ function expectEditorIdentity(
87
+ view: EditorView,
88
+ identity: ReturnType<typeof getEditorIdentity>,
89
+ ) {
90
+ expect(view.state.doc).toBe(identity.doc);
91
+ expect(view.dom).toBe(identity.dom);
92
+ expect(view.contentDOM).toBe(identity.contentDOM);
93
+ expect(view.scrollDOM).toBe(identity.scrollDOM);
94
+ }
95
+
29
96
  describe("ReadonlyCode", () => {
97
+ it("reconfigures the theme without replacing the editor", async () => {
98
+ let createdView: EditorView | undefined;
99
+ const onCreateEditor = (view: EditorView) => {
100
+ createdView = view;
101
+ };
102
+ const renderCode = (id: string) => (
103
+ <TooltipProvider>
104
+ <ReadonlyCode
105
+ id={id}
106
+ code={"line_1 = 1\nline_2 = 2\nline_3 = 3"}
107
+ showCopyCode={false}
108
+ onCreateEditor={onCreateEditor}
109
+ />
110
+ </TooltipProvider>
111
+ );
112
+
113
+ const { rerender } = render(renderCode("light"));
114
+ await waitFor(() => {
115
+ expect(createdView).toBeDefined();
116
+ });
117
+
118
+ const view = createdView;
119
+ if (!view) {
120
+ throw new Error("ReadonlyCode did not create an editor view");
121
+ }
122
+ const identity = getEditorIdentity(view);
123
+ expect(view.state.facet(EditorView.darkTheme)).toBe(false);
124
+
125
+ themeState.value = "dark";
126
+ rerender(renderCode("dark"));
127
+ await waitFor(() => {
128
+ expect(view.state.facet(EditorView.darkTheme)).toBe(true);
129
+ });
130
+ expect(createdView).toBe(view);
131
+ expectEditorIdentity(view, identity);
132
+
133
+ themeState.value = "light";
134
+ rerender(renderCode("light-again"));
135
+ await waitFor(() => {
136
+ expect(view.state.facet(EditorView.darkTheme)).toBe(false);
137
+ });
138
+ expect(createdView).toBe(view);
139
+ expectEditorIdentity(view, identity);
140
+ });
141
+
30
142
  it("starts collapsed when initiallyHideCode is true", () => {
31
143
  const { container } = renderReadonly({ initiallyHideCode: true });
32
144
  expect(isCollapsed(container)).toBe(true);
@@ -154,6 +154,20 @@ _creds = json.loads(_credentials_json)
154
154
  fs = GoogleDriveFileSystem(creds=_creds, token="service_account", use_listings_cache=False, skip_instance_cache=True)"
155
155
  `;
156
156
 
157
+ exports[`generateStorageCode > Hugging Face > default connection 1`] = `
158
+ "from huggingface_hub import HfApi
159
+
160
+ hf = HfApi()"
161
+ `;
162
+
163
+ exports[`generateStorageCode > Hugging Face > with token from secrets 1`] = `
164
+ "from huggingface_hub import HfApi
165
+ import os
166
+
167
+ _token = os.environ.get("HF_TOKEN")
168
+ hf = HfApi(token=_token)"
169
+ `;
170
+
157
171
  exports[`generateStorageCode > S3 > basic connection with all fields 1`] = `
158
172
  "from obstore.store import S3Store
159
173
 
@@ -309,6 +309,26 @@ describe("generateStorageCode", () => {
309
309
  });
310
310
  });
311
311
 
312
+ describe("Hugging Face", () => {
313
+ it("default connection", () => {
314
+ expect(
315
+ generateStorageCode({ type: "huggingface" }, { library: "fsspec" }),
316
+ ).toMatchSnapshot();
317
+ });
318
+
319
+ it("with token from secrets", () => {
320
+ expect(
321
+ generateStorageCode(
322
+ {
323
+ type: "huggingface",
324
+ token: prefixSecret("HF_TOKEN"),
325
+ },
326
+ { library: "fsspec" },
327
+ ),
328
+ ).toMatchSnapshot();
329
+ });
330
+ });
331
+
312
332
  describe("invalid cases", () => {
313
333
  it("throws for empty S3 bucket", () => {
314
334
  expect(() =>
@@ -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>;