@marimo-team/frontend 0.25.2 → 0.25.3-dev100

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 (476) 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-Gl5nCA8C.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-BbOqmHy3.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-8UBC0xwR.js → add-cell-with-ai-Clg5FELx.js} +12 -12
  12. package/dist/assets/{add-connection-dialog-DXQnd6eB.js → add-connection-dialog-BqdWmD-7.js} +2 -2
  13. package/dist/assets/{agent-panel-D7kMZLG-.js → agent-panel-5RxG5Y82.js} +5 -5
  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-BKEG4x9u.js → app-config-button-0QTeQ2Mv.js} +1 -1
  18. package/dist/assets/arrays-0R2reF2y.js +1 -0
  19. package/dist/assets/{blob-D5nfNHOo.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-BRkCaSsh.js → cells-D_11MjVB.js} +60 -60
  26. package/dist/assets/{chat-display-Dnagy4Nv.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-Cu6CDbuw.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-CGw16R_N.js → components-G6frbw0c.js} +1 -1
  38. package/dist/assets/config-CD1IuZFJ.js +1 -0
  39. package/dist/assets/config-DMz7oN0r.js +1 -0
  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-Cq6x_5ML.js → datasource-deuXnlU-.js} +1 -1
  45. package/dist/assets/{dates-BbFQ2oE4.js → dates-CQMwFko2.js} +1 -1
  46. package/dist/assets/{dependency-graph-panel--Kgtktcw.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-BHmbeznw.js → documentation-panel-2PQ8kQLa.js} +1 -1
  49. package/dist/assets/{download-DCJXy66H.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-Dk3VZ-OK.js → error-entries-3GVk2KjS.js} +1 -1
  54. package/dist/assets/{error-panel-BPRYmC5J.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-Bik2-e33.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-Cdc-TVqO.js → file-name-input-DqwMSgGU.js} +1 -1
  61. package/dist/assets/{floating-outline-XD9FAV-f.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-Dm6mGwBi.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-J9CAb70d.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-93IwHd2u.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-ABM16htT.js → logs-panel-B5eVAZtH.js} +1 -1
  80. package/dist/assets/{markdown-renderer-BG3sp2rs.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-BgEBmqyL.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-DCXQ3_F6.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-C1sdPp0m.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-Do50PHI8.js → readonly-code-display-DYcZQ28T.js} +1 -1
  97. package/dist/assets/{readonly-python-code-DCSMIsTX.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-ltaepAqw.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-CSPobEdR.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-Bh1oTw4m.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-XsPO8hNi.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-GM5E_bUs.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-GR7eEQrT.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-2_bMClHd.js → useDeleteCell-BC-MW7d1.js} +1 -1
  142. package/dist/assets/{useDependencyPanelTab-k3C3M18S.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-B2vUhREg.js → useRunCells-ByaWxoIt.js} +1 -1
  150. package/dist/assets/{useSplitCell-DEb03htp.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 -79
  161. package/package.json +8 -5
  162. package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
  163. package/src/__tests__/host-imports.test.ts +101 -0
  164. package/src/__tests__/mount.test.ts +8 -8
  165. package/src/components/app-config/ai-config.tsx +7 -2
  166. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  167. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  168. package/src/components/chat/acp/blocks.tsx +2 -2
  169. package/src/components/chat/chat-components.tsx +2 -0
  170. package/src/components/chat/chat-panel.tsx +9 -8
  171. package/src/components/chat/chat-utils.ts +4 -8
  172. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  173. package/src/components/data-table/__tests__/columns.test.tsx +105 -0
  174. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  175. package/src/components/data-table/__tests__/context-menu.test.ts +176 -0
  176. package/src/components/data-table/__tests__/date-filter-inputs.test.tsx +24 -1
  177. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  178. package/src/components/data-table/__tests__/export-actions.test.tsx +434 -6
  179. package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
  180. package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
  181. package/src/components/data-table/cell-selection/feature.ts +2 -2
  182. package/src/components/data-table/cell-selection/types.ts +2 -2
  183. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
  184. package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
  185. package/src/components/data-table/charts/charts.tsx +35 -25
  186. package/src/components/data-table/charts/storage.ts +16 -5
  187. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  188. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  189. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  190. package/src/components/data-table/columns.tsx +45 -46
  191. package/src/components/data-table/context-menu.tsx +161 -46
  192. package/src/components/data-table/data-table.tsx +7 -1
  193. package/src/components/data-table/date-filter-inputs.tsx +15 -42
  194. package/src/components/data-table/date-popover.tsx +6 -4
  195. package/src/components/data-table/export-actions.tsx +267 -163
  196. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +2 -2
  197. package/src/components/data-table/schemas.ts +14 -36
  198. package/src/components/data-table/search-highlight.tsx +79 -0
  199. package/src/components/data-table/types.ts +1 -0
  200. package/src/components/data-table/url-detector.tsx +49 -9
  201. package/src/components/data-table/utils.ts +18 -0
  202. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  203. package/src/components/datasources/__tests__/column-preview.test.tsx +63 -2
  204. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  205. package/src/components/datasources/column-preview.tsx +8 -2
  206. package/src/components/datasources/datasources.tsx +47 -35
  207. package/src/components/debugger/debugger-code.tsx +5 -0
  208. package/src/components/dependency-graph/panels.tsx +2 -1
  209. package/src/components/editor/Output.tsx +9 -2
  210. package/src/components/editor/SortableCell.tsx +1 -3
  211. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  212. package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +80 -252
  213. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +210 -34
  214. package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
  215. package/src/components/editor/actions/pair-with-agent-modal.tsx +100 -37
  216. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  217. package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
  218. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  219. package/src/components/editor/cell/CreateCellButton.tsx +8 -10
  220. package/src/components/editor/cell/DeleteButton.tsx +1 -0
  221. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  222. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  223. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  224. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  225. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  226. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
  227. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  228. package/src/components/editor/chrome/panels/error-panel.tsx +1 -0
  229. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  230. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +366 -0
  231. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +58 -1
  232. package/src/components/editor/chrome/wrapper/footer-item.tsx +32 -24
  233. package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +237 -0
  234. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  235. package/src/components/editor/chrome/wrapper/footer.tsx +2 -0
  236. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
  237. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  238. package/src/components/editor/connections/components.tsx +1 -1
  239. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  240. package/src/components/editor/controls/command-palette.tsx +22 -10
  241. package/src/components/editor/dynamic-favicon.tsx +21 -40
  242. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +537 -0
  243. package/src/components/editor/errors/send-error-report-button.tsx +69 -0
  244. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  245. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  246. package/src/components/editor/file-tree/file-explorer.tsx +10 -5
  247. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  248. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  249. package/src/components/editor/notebook-banner.tsx +1 -0
  250. package/src/components/editor/output/MarimoErrorOutput.tsx +21 -5
  251. package/src/components/editor/output/MarimoTracebackOutput.tsx +37 -22
  252. package/src/components/editor/output/__tests__/ansi-reduce.test.ts +18 -0
  253. package/src/components/editor/output/ansi-reduce.ts +12 -0
  254. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  255. package/src/components/editor/package-alert.tsx +6 -4
  256. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  257. package/src/components/home/components.tsx +17 -31
  258. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  259. package/src/components/icons/large-spinner.tsx +3 -14
  260. package/src/components/icons/spinner.tsx +1 -1
  261. package/src/components/mcp/mcp-status-indicator.tsx +1 -1
  262. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  263. package/src/components/pages/home-page.tsx +164 -91
  264. package/src/components/scratchpad/scratchpad.tsx +2 -2
  265. package/src/components/slides/reveal-component.tsx +4 -2
  266. package/src/components/slides/slide-cell-view.tsx +1 -1
  267. package/src/components/slides/slide-form.tsx +1 -0
  268. package/src/components/tracing/tracing.tsx +2 -2
  269. package/src/components/ui/alert-dialog.tsx +1 -1
  270. package/src/core/ai/context/providers/datasource.ts +6 -2
  271. package/src/core/attachments/__tests__/toast.test.tsx +58 -0
  272. package/src/core/attachments/state.ts +18 -0
  273. package/src/core/attachments/toast.ts +15 -0
  274. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -0
  275. package/src/core/cells/__tests__/cell.test.ts +39 -0
  276. package/src/core/cells/__tests__/cells.test.ts +55 -1
  277. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
  278. package/src/core/cells/__tests__/error-report.test.ts +139 -0
  279. package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
  280. package/src/core/cells/__tests__/session.test.ts +50 -1
  281. package/src/core/cells/cell.ts +14 -3
  282. package/src/core/cells/cells.ts +3 -1
  283. package/src/core/cells/error-report.ts +63 -0
  284. package/src/core/cells/pending-delete-service.ts +2 -6
  285. package/src/core/cells/session.ts +29 -2
  286. package/src/core/cells/types.ts +10 -1
  287. package/src/core/codemirror/__tests__/setup.test.ts +1 -1
  288. package/src/core/codemirror/keymaps/vim.ts +2 -6
  289. package/src/core/codemirror/language/languages/markdown.ts +1 -1
  290. package/src/core/codemirror/language/languages/python.ts +3 -3
  291. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  292. package/src/core/config/pair.ts +5 -3
  293. package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
  294. package/src/core/datasets/data-source-discovery.ts +4 -6
  295. package/src/core/document/__tests__/draft.test.ts +322 -0
  296. package/src/core/document/__tests__/fixtures.ts +92 -0
  297. package/src/core/document/__tests__/model.test.ts +483 -0
  298. package/src/core/document/__tests__/state.test.ts +446 -0
  299. package/src/core/document/__tests__/submission.test.ts +346 -0
  300. package/src/core/document/draft.ts +472 -0
  301. package/src/core/document/index.ts +10 -0
  302. package/src/core/document/model.ts +53 -0
  303. package/src/core/document/state.ts +401 -0
  304. package/src/core/document/sync.ts +359 -0
  305. package/src/core/edit-app.tsx +2 -2
  306. package/src/core/host/AGENTS.md +16 -0
  307. package/src/core/host/__tests__/attachments.test.ts +56 -0
  308. package/src/core/host/__tests__/document.test.ts +126 -0
  309. package/src/core/host/__tests__/fixtures.ts +41 -0
  310. package/src/core/host/__tests__/notebook-client.test.ts +570 -0
  311. package/src/core/host/connection.ts +42 -0
  312. package/src/core/host/notebook-client.ts +297 -0
  313. package/src/core/host/resources/attachments.ts +22 -0
  314. package/src/core/host/resources/document.ts +101 -0
  315. package/src/core/hotkeys/actions.ts +2 -4
  316. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  317. package/src/core/network/connection.ts +4 -2
  318. package/src/core/network/useConnectionNotice.ts +8 -2
  319. package/src/core/run-app.tsx +2 -2
  320. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  321. package/src/core/runtime/__tests__/runtime.test.ts +18 -17
  322. package/src/core/runtime/runtime.ts +26 -30
  323. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  324. package/src/core/state/jotai.ts +1 -1
  325. package/src/core/static/files.ts +2 -0
  326. package/src/core/wasm/bridge.ts +2 -10
  327. package/src/core/wasm/store.ts +3 -3
  328. package/src/core/wasm/worker/bootstrap.ts +1 -1
  329. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  330. package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -29
  331. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +223 -0
  332. package/src/core/websocket/__tests__/{useMarimoKernelConnection.hook.test.tsx → useNotebookSession.hook.test.tsx} +194 -184
  333. package/src/core/websocket/connection-state.ts +60 -0
  334. package/src/core/websocket/state.ts +7 -0
  335. package/src/core/websocket/transports/__tests__/notebook.test.ts +502 -0
  336. package/src/core/websocket/transports/notebook.ts +213 -0
  337. package/src/core/websocket/types.ts +9 -39
  338. package/src/core/websocket/useNotebookClient.ts +89 -0
  339. package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +53 -224
  340. package/src/css/globals.css +5 -0
  341. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  342. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  343. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  344. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  345. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  346. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  347. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  348. package/src/hooks/useAsyncData.ts +3 -3
  349. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  350. package/src/hooks/useDebounce.ts +5 -8
  351. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  352. package/src/hooks/useDelayElapsed.ts +14 -10
  353. package/src/hooks/useElapsedTime.ts +3 -3
  354. package/src/hooks/useInputHistory.ts +4 -7
  355. package/src/hooks/useInternalStateWithSync.ts +6 -6
  356. package/src/hooks/useResizeObserver.ts +4 -7
  357. package/src/hooks/useScript.ts +67 -70
  358. package/src/hooks/useStateForKey.ts +93 -0
  359. package/src/mount.tsx +3 -3
  360. package/src/plugins/core/registerReactComponent.tsx +1 -1
  361. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  362. package/src/plugins/impl/DataTablePlugin.tsx +14 -15
  363. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  364. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  365. package/src/plugins/impl/FormPlugin.tsx +1 -1
  366. package/src/plugins/impl/RangeSliderPlugin.tsx +0 -1
  367. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  368. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  369. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +201 -1
  370. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  371. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  372. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
  373. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  374. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  375. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  376. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  377. package/src/plugins/impl/chat/chat-ui.tsx +12 -10
  378. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  379. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  380. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  381. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  382. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  383. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +0 -1
  384. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  385. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  386. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  387. package/src/plugins/impl/panel/utils.ts +17 -0
  388. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  389. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  390. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  391. package/src/stories/button.stories.tsx +1 -0
  392. package/src/stories/editor.stories.tsx +24 -17
  393. package/src/stories/form-wrapper.stories.tsx +2 -2
  394. package/src/stories/select.stories.tsx +2 -2
  395. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  396. package/src/utils/__tests__/arrays.test.ts +35 -0
  397. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  398. package/src/utils/__tests__/dates.test.ts +116 -0
  399. package/src/utils/__tests__/numbers.test.ts +48 -1
  400. package/src/utils/__tests__/objects.test.ts +21 -0
  401. package/src/utils/arrays.ts +10 -0
  402. package/src/utils/blob.ts +1 -1
  403. package/src/utils/dates.ts +94 -0
  404. package/src/utils/numbers.ts +28 -17
  405. package/src/utils/objects.ts +3 -7
  406. package/src/utils/once.ts +19 -19
  407. package/src/utils/zod-utils.ts +1 -3
  408. package/dist/assets/CellStatus-BP2mavAq.js +0 -1
  409. package/dist/assets/JsonOutput-D6HIIsCb.js +0 -11
  410. package/dist/assets/MarimoErrorOutput-D60XkCny.js +0 -6
  411. package/dist/assets/RunButton-CK3LhnP5.js +0 -1
  412. package/dist/assets/__vite-browser-external-CYO_P3S8.js +0 -1
  413. package/dist/assets/__vite-browser-external-FeDVGZU8.js +0 -1
  414. package/dist/assets/ai-model-dropdown-B0z6-zhp.js +0 -5
  415. package/dist/assets/any-language-editor-BLCAD3yx.js +0 -1
  416. package/dist/assets/api-BeC_VSGv.js +0 -1
  417. package/dist/assets/arrays-BqUx5USx.js +0 -1
  418. package/dist/assets/cell-editor-ADVfycpN.js +0 -21
  419. package/dist/assets/cell-link-LSth_8hT.js +0 -1
  420. package/dist/assets/chat-panel-DQ6HkWEF.js +0 -4
  421. package/dist/assets/chat-ui-Df8Vcj6n.js +0 -4
  422. package/dist/assets/circle-play-CYIb0qT6.js +0 -1
  423. package/dist/assets/code-visibility-B-_GvwR5.js +0 -1
  424. package/dist/assets/coerce-CpE6kUdX.js +0 -1
  425. package/dist/assets/column-preview-Cm2D6vyX.js +0 -1
  426. package/dist/assets/command-palette-DTSQ7gOC.js +0 -1
  427. package/dist/assets/components-CXfO-F47.js +0 -1
  428. package/dist/assets/config-BOKzA3tF.js +0 -1
  429. package/dist/assets/config-DX8Y2WbJ.js +0 -1
  430. package/dist/assets/config-VPSq73n8.js +0 -1
  431. package/dist/assets/edit-page-ChcSgpke.js +0 -9
  432. package/dist/assets/feedback-button-B2gKC5cn.js +0 -1
  433. package/dist/assets/file-explorer-panel-BEEK5lcl.js +0 -1
  434. package/dist/assets/home-page-DMHnsRxm.js +0 -4
  435. package/dist/assets/index-Bcdzex-3.css +0 -2
  436. package/dist/assets/index-s-M1OXjs.js +0 -29
  437. package/dist/assets/kiosk-mode-BZECfg9k.js +0 -1
  438. package/dist/assets/layout-DmcNEu3P.js +0 -9
  439. package/dist/assets/linear-0eij_aAG.js +0 -1
  440. package/dist/assets/link-NuFVR7G9.js +0 -1
  441. package/dist/assets/links-DkkL9DVa.js +0 -1
  442. package/dist/assets/numbers-Mk3-CcoU.js +0 -1
  443. package/dist/assets/packages-panel-BMGOmZkR.js +0 -1
  444. package/dist/assets/pair-CaQyK6En.js +0 -1
  445. package/dist/assets/panels-CxVG8-v2.js +0 -4
  446. package/dist/assets/precisionRound-Bq_nmf5u.js +0 -1
  447. package/dist/assets/process-output-D0KGq04R.js +0 -1
  448. package/dist/assets/react-vega-C3rYGtP6.js +0 -189
  449. package/dist/assets/requests-CgSY1X4a.js +0 -1
  450. package/dist/assets/run-page-DF3c5cDk.js +0 -1
  451. package/dist/assets/scratchpad-panel-Dwd6W5PE.js +0 -1
  452. package/dist/assets/session-panel-VdDhEBa-.js +0 -1
  453. package/dist/assets/state-BAJYRhmO.js +0 -1
  454. package/dist/assets/state-_Qai_QyX.js +0 -3
  455. package/dist/assets/tooltip-mT3CGest.js +0 -61
  456. package/dist/assets/tracing-Di6pUmX8.js +0 -1
  457. package/dist/assets/tracing-panel-PeRXHD8_.js +0 -2
  458. package/dist/assets/useAsyncData-C7aJJz0Y.js +0 -1
  459. package/dist/assets/useBoolean-BDD6_xQQ.js +0 -1
  460. package/dist/assets/useCellActionButton-B-9eG5z_.js +0 -1
  461. package/dist/assets/useDebounce-B_ccBZTp.js +0 -1
  462. package/dist/assets/useNotebookActions-NHN6A-cN.js +0 -1
  463. package/dist/assets/vega-component-CnadTgoE.js +0 -1
  464. package/dist/assets/vega-loader.browser-CLRkNgm7.js +0 -6
  465. package/dist/assets/ws-BXWY541V.js +0 -22
  466. package/src/core/state/__tests__/jotai.test.ts +0 -25
  467. package/src/core/websocket/__tests__/useWebSocket.test.ts +0 -49
  468. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  469. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -338
  470. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  471. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  472. package/src/core/websocket/transports/basic.ts +0 -102
  473. package/src/core/websocket/transports/sse.ts +0 -308
  474. package/src/core/websocket/transports/transport.ts +0 -61
  475. package/src/core/websocket/transports/ws.ts +0 -98
  476. package/src/core/websocket/useWebSocket.tsx +0 -88
@@ -14,6 +14,7 @@ import {
14
14
  TableIcon,
15
15
  } from "lucide-react";
16
16
  import React from "react";
17
+ import useEvent from "react-use-event-hook";
17
18
  import { downloadSizeLimitAtom } from "./download-policy/atoms";
18
19
  import type {
19
20
  DownloadAsArgs,
@@ -22,6 +23,8 @@ import type {
22
23
  ExportMetadata,
23
24
  GetExportMetadata,
24
25
  } from "./schemas";
26
+ import { useAsyncData } from "@/hooks/useAsyncData";
27
+ import { useStateForKey } from "@/hooks/useStateForKey";
25
28
  import { logNever } from "@/utils/assertNever";
26
29
  import { cn } from "@/utils/cn";
27
30
  import { copyToClipboard } from "@/utils/copy";
@@ -274,19 +277,133 @@ const failureTitleForAction = (action: ExportAction): string =>
274
277
  const failureDescription = (error: unknown): string =>
275
278
  typeof error === "string" ? error : prettyError(error);
276
279
 
280
+ const OBSOLETE_GEOMETRY_MESSAGE =
281
+ "The selected geometry is no longer available. Choose another geometry to export.";
282
+
283
+ type GeographicBlocker =
284
+ | "format-unavailable"
285
+ | "geometry-required"
286
+ | "choose-geometry"
287
+ | "obsolete-geometry"
288
+ | "missing-crs";
289
+
290
+ /**
291
+ * Find the first reason that a geographic export cannot start with the
292
+ * selected geometry. The dialog uses it to disable a format, and the retry
293
+ * after a package installation uses it to decide whether to export.
294
+ */
295
+ function geographicBlocker(
296
+ metadata: ExportMetadata,
297
+ format: GeographicFormat,
298
+ geometryColumn: string | null,
299
+ ): GeographicBlocker | null {
300
+ if (metadata.formats[format]?.available === false) {
301
+ return "format-unavailable";
302
+ }
303
+ const selected = metadata.geometry_columns.find(
304
+ (column) => column.name === geometryColumn,
305
+ );
306
+ if (geometryColumn !== null && !selected) {
307
+ return "obsolete-geometry";
308
+ }
309
+ if (metadata.geometry_columns.length === 0) {
310
+ return format === "geojson" ? "geometry-required" : null;
311
+ }
312
+ if (!selected) {
313
+ return "choose-geometry";
314
+ }
315
+ if (format === "geojson" && !selected.crs) {
316
+ return "missing-crs";
317
+ }
318
+ return null;
319
+ }
320
+
321
+ const geometryDisplayName = (name: string | null): string | null =>
322
+ name === "" ? "(unnamed geometry)" : name;
323
+
324
+ function geographicBlockerReason(
325
+ blocker: GeographicBlocker | null,
326
+ formatLabel: string,
327
+ geometryColumn: string | null,
328
+ ): string | null {
329
+ switch (blocker) {
330
+ case "choose-geometry":
331
+ return `Choose a primary geometry column to export ${formatLabel}.`;
332
+ case "obsolete-geometry":
333
+ return OBSOLETE_GEOMETRY_MESSAGE;
334
+ case "missing-crs":
335
+ return `Geometry column ${geometryDisplayName(geometryColumn)} needs a CRS for GeoJSON export. Declare its source CRS in Python before exporting.`;
336
+ case "format-unavailable":
337
+ case "geometry-required":
338
+ case null:
339
+ return null;
340
+ default:
341
+ logNever(blocker);
342
+ return null;
343
+ }
344
+ }
345
+
346
+ /**
347
+ * One open dialog for one table source. Closing the dialog or replacing the
348
+ * source creates a new context, and work started under an older context must
349
+ * not change the dialog or start an export.
350
+ */
351
+ interface ExportContext {
352
+ source: unknown;
353
+ }
354
+
277
355
  export const ExportActions: React.FC<ExportActionProps> = (props) => {
278
356
  const { getExportMetadata, metadataSource } = props;
279
357
  const [exportDialogOpen, setExportDialogOpen] = React.useState(false);
280
- const [failure, setFailure] = React.useState<ExportFailure | null>(null);
281
358
  const [settings, setSettings] =
282
359
  React.useState<ExportSettings>(DEFAULT_SETTINGS);
283
- const [metadata, setMetadata] = React.useState<ExportMetadata | null>(null);
284
- const [metadataLoading, setMetadataLoading] = React.useState(false);
285
- const [metadataError, setMetadataError] = React.useState<string | null>(null);
286
- const [metadataRefresh, setMetadataRefresh] = React.useState(0);
360
+ const [exportContext, setExportContext] = React.useState<ExportContext>(
361
+ () => ({ source: metadataSource }),
362
+ );
363
+ if (exportContext.source !== metadataSource) {
364
+ setExportContext({ source: metadataSource });
365
+ }
366
+ const [failure, setFailure] = useStateForKey<ExportFailure | null>(
367
+ exportContext,
368
+ () => null,
369
+ );
370
+ const mounted = React.useRef(false);
371
+ const isCurrentContext = useEvent(
372
+ (context: ExportContext) => mounted.current && context === exportContext,
373
+ );
374
+ const metadataRequest = useAsyncData(async () => {
375
+ if (!exportDialogOpen || !getExportMetadata) {
376
+ return null;
377
+ }
378
+ return { context: exportContext, metadata: await getExportMetadata({}) };
379
+ }, [exportDialogOpen, getExportMetadata, exportContext]);
380
+ // The hook keeps earlier data while it reloads and after a failure. Only a
381
+ // completed response for the current context can enable an export.
382
+ const metadata =
383
+ metadataRequest.status === "success" &&
384
+ metadataRequest.data?.context === exportContext
385
+ ? metadataRequest.data.metadata
386
+ : null;
387
+ const metadataError =
388
+ metadataRequest.status === "error"
389
+ ? failureDescription(metadataRequest.error)
390
+ : null;
287
391
  const [geometryColumn, setGeometryColumn] = React.useState<string | null>(
288
392
  null,
289
393
  );
394
+ const [reconciledMetadata, setReconciledMetadata] =
395
+ React.useState<ExportMetadata | null>(null);
396
+ if (metadata !== reconciledMetadata) {
397
+ setReconciledMetadata(metadata);
398
+ if (metadata) {
399
+ setGeometryColumn((current) =>
400
+ current !== null &&
401
+ metadata.geometry_columns.some((column) => column.name === current)
402
+ ? current
403
+ : metadata.default_geometry_column,
404
+ );
405
+ }
406
+ }
290
407
  const hasGeometry = !!metadata?.geometry_columns.length;
291
408
  const [expandedFormat, setExpandedFormat] = React.useState<
292
409
  ConfigurableFormat | GeographicFormat | null
@@ -294,43 +411,16 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
294
411
  const triggerRef = React.useRef<HTMLButtonElement>(null);
295
412
  const dialogRef = React.useRef<HTMLDivElement>(null);
296
413
  const latestActionId = React.useRef(0);
414
+ React.useLayoutEffect(() => {
415
+ mounted.current = true;
416
+ return () => {
417
+ mounted.current = false;
418
+ latestActionId.current += 1;
419
+ };
420
+ }, []);
297
421
  React.useEffect(() => {
298
422
  latestActionId.current += 1;
299
423
  }, [metadataSource]);
300
- React.useEffect(() => {
301
- if (!exportDialogOpen || !getExportMetadata) {
302
- return;
303
- }
304
- let active = true;
305
- setMetadata(null);
306
- setMetadataLoading(true);
307
- setMetadataError(null);
308
- void getExportMetadata({}).then(
309
- (result) => {
310
- if (!active) {
311
- return;
312
- }
313
- setMetadata(result);
314
- setGeometryColumn((current) =>
315
- current !== null &&
316
- result.geometry_columns.some((column) => column.name === current)
317
- ? current
318
- : result.default_geometry_column,
319
- );
320
- setMetadataLoading(false);
321
- },
322
- (error: unknown) => {
323
- if (!active) {
324
- return;
325
- }
326
- setMetadataError(failureDescription(error));
327
- setMetadataLoading(false);
328
- },
329
- );
330
- return () => {
331
- active = false;
332
- };
333
- }, [exportDialogOpen, getExportMetadata, metadataSource, metadataRefresh]);
334
424
  const policy = useAtomValue(downloadSizeLimitAtom);
335
425
  const overLimit = !!(
336
426
  policy &&
@@ -365,12 +455,10 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
365
455
  setExportDialogOpen(open);
366
456
  if (!open) {
367
457
  latestActionId.current += 1;
368
- setFailure(null);
369
458
  setExpandedFormat(null);
370
459
  setSettings(DEFAULT_SETTINGS);
371
- setMetadata(null);
372
- setMetadataError(null);
373
460
  setGeometryColumn(null);
461
+ setExportContext({ source: metadataSource });
374
462
  }
375
463
  };
376
464
 
@@ -553,57 +641,67 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
553
641
  }
554
642
  };
555
643
 
556
- const retryAfterInstall = async (action: ExportAction) => {
557
- if (isGeographic(action.format) && getExportMetadata) {
644
+ // Package installation can finish after the dialog closes or the source
645
+ // changes, so the retry carries the context in which installation began.
646
+ const retryAfterInstall = useEvent(
647
+ async (action: ExportAction, context: ExportContext) => {
648
+ if (!isCurrentContext(context)) {
649
+ return;
650
+ }
651
+ if (!isGeographic(action.format) || !getExportMetadata) {
652
+ retryAction(action);
653
+ return;
654
+ }
558
655
  const actionId = latestActionId.current;
656
+ const isCurrent = () =>
657
+ isCurrentContext(context) && actionId === latestActionId.current;
559
658
  let result: ExportMetadata;
560
659
  try {
561
660
  result = await getExportMetadata({});
562
- } catch (error) {
563
- if (actionId === latestActionId.current) {
564
- setMetadataError(failureDescription(error));
661
+ } catch {
662
+ if (isCurrent()) {
663
+ metadataRequest.refetch();
565
664
  }
566
665
  return;
567
666
  }
568
- if (actionId !== latestActionId.current) {
569
- return;
570
- }
571
- setMetadata(result);
572
- setMetadataError(null);
573
- if (result.formats[action.format]?.available === false) {
574
- setFailure(null);
667
+ if (!isCurrent()) {
575
668
  return;
576
669
  }
577
- if (
578
- geometryColumn !== null &&
579
- !result.geometry_columns.some(
580
- (column) => column.name === geometryColumn,
581
- )
582
- ) {
583
- setGeometryColumn(result.default_geometry_column);
584
- setActionFailure(actionId, {
585
- kind: "error",
586
- title: failureTitleForAction(action),
587
- description:
588
- "The selected geometry is no longer available. Choose another geometry to export.",
589
- action,
590
- });
591
- return;
592
- }
593
- if (result.geometry_columns.length > 0 && geometryColumn === null) {
594
- return;
595
- }
596
- if (
597
- action.format === "geojson" &&
598
- !result.geometry_columns.find(
599
- (column) => column.name === geometryColumn,
600
- )?.crs
601
- ) {
602
- setFailure(null);
603
- return;
670
+ // Validation reads its own response, so a display refresh cannot
671
+ // start an export that this check rejected.
672
+ metadataRequest.refetch();
673
+ const blocker = geographicBlocker(result, action.format, geometryColumn);
674
+ switch (blocker) {
675
+ case null:
676
+ retryAction(action);
677
+ return;
678
+ case "obsolete-geometry":
679
+ setGeometryColumn(result.default_geometry_column);
680
+ setActionFailure(actionId, {
681
+ kind: "error",
682
+ title: failureTitleForAction(action),
683
+ description: OBSOLETE_GEOMETRY_MESSAGE,
684
+ action,
685
+ });
686
+ return;
687
+ case "choose-geometry":
688
+ return;
689
+ case "format-unavailable":
690
+ case "geometry-required":
691
+ case "missing-crs":
692
+ setFailure(null);
693
+ return;
694
+ default:
695
+ logNever(blocker);
604
696
  }
605
- }
606
- retryAction(action);
697
+ },
698
+ );
699
+
700
+ const createInstallRetry = (action: ExportAction) => {
701
+ const context = exportContext;
702
+ return () => {
703
+ void retryAfterInstall(action, context);
704
+ };
607
705
  };
608
706
 
609
707
  const toggleExpanded = (format: ExportFormat) => {
@@ -665,14 +763,15 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
665
763
  const expanded =
666
764
  configurableFormat !== null &&
667
765
  expandedFormat === configurableFormat;
766
+ const FormatHeader = configurableFormat ? "button" : "div";
668
767
  const panelId = `export-options-${option.format}`;
768
+ const descriptionId = `${panelId}-description`;
769
+ const summaryId = `${panelId}-summary`;
669
770
  const rowFailure =
670
771
  failure?.action.format === option.format ? failure : null;
671
772
  const summary =
672
773
  isGeographic(option.format) && hasGeometry
673
- ? geometryColumn === ""
674
- ? "(unnamed geometry)"
675
- : (geometryColumn ?? "Choose geometry")
774
+ ? (geometryDisplayName(geometryColumn) ?? "Choose geometry")
676
775
  : settingsSummary(settings, option.format);
677
776
  const selectedGeometryIndex =
678
777
  metadata?.geometry_columns.findIndex(
@@ -681,20 +780,14 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
681
780
  const eligibility = isGeographic(option.format)
682
781
  ? metadata?.formats[option.format]
683
782
  : undefined;
684
- const selectedGeometry =
685
- metadata?.geometry_columns[selectedGeometryIndex];
686
- const missingCrs =
687
- option.format === "geojson" &&
688
- selectedGeometry !== undefined &&
689
- !selectedGeometry.crs;
783
+ const blocker =
784
+ isGeographic(option.format) && metadata
785
+ ? geographicBlocker(metadata, option.format, geometryColumn)
786
+ : null;
690
787
  const geometryReason = metadataError
691
788
  ? `Could not load geometry options: ${metadataError}`
692
789
  : (eligibility?.reason ??
693
- (hasGeometry && geometryColumn === null
694
- ? `Choose a primary geometry column to export ${optionLabel}.`
695
- : missingCrs
696
- ? `Geometry column ${geometryColumn === "" ? "(unnamed geometry)" : geometryColumn} needs a CRS for GeoJSON export. Declare its source CRS in Python before exporting.`
697
- : null));
790
+ geographicBlockerReason(blocker, optionLabel, geometryColumn));
698
791
  const missingPackageFailure =
699
792
  rowFailure?.kind === "missing-packages" ? rowFailure : null;
700
793
  const missingPackages =
@@ -704,13 +797,7 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
704
797
  : []);
705
798
  const geographicUnavailable =
706
799
  isGeographic(option.format) &&
707
- ((!!getExportMetadata && !metadata) ||
708
- metadataLoading ||
709
- !!metadataError ||
710
- eligibility?.available === false ||
711
- missingCrs ||
712
- (!!metadata?.geometry_columns.length &&
713
- geometryColumn === null));
800
+ ((!!getExportMetadata && !metadata) || blocker !== null);
714
801
  const copyUnavailable =
715
802
  isGeographic(option.format) &&
716
803
  (geographicUnavailable || missingPackages.length > 0);
@@ -720,55 +807,78 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
720
807
  data-testid={`export-row-${option.format}`}
721
808
  className={cn(expanded && "bg-muted/40")}
722
809
  >
723
- <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">
724
- {configurableFormat ? (
725
- <Button
726
- type="button"
727
- variant="ghost"
728
- size="icon"
729
- aria-label={`${optionLabel} options`}
730
- aria-expanded={expanded}
731
- aria-controls={panelId}
732
- 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"
733
839
  >
734
- <ChevronRightIcon
735
- className={cn(
736
- "h-4 w-4 text-muted-foreground transition-transform",
737
- expanded && "rotate-90",
738
- )}
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"
739
854
  />
740
- </Button>
741
- ) : (
742
- <span aria-hidden={true} className="h-6 w-6" />
743
- )}
744
- <div className="flex min-w-0 items-center gap-3 pl-1">
745
- <option.icon
746
- aria-hidden={true}
747
- data-testid={`export-format-icon-${option.format}`}
748
- className="h-4 w-4 shrink-0 text-muted-foreground"
749
- />
750
- <div className="grid min-w-0 gap-1">
751
- <span className="flex min-w-0 items-baseline gap-x-2">
752
- <span className="text-sm font-medium leading-tight">
753
- {optionLabel}
754
- </span>
755
- {summary && (
756
- <span
757
- data-testid={`export-summary-${option.format}`}
758
- className="min-w-0 truncate text-xs text-muted-foreground"
759
- >
760
- {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}
761
859
  </span>
762
- )}
763
- </span>
764
- <span className="text-xs text-muted-foreground leading-tight">
765
- {option.format === "parquet" && hasGeometry
766
- ? "Parquet with geometry and CRS metadata"
767
- : 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>
768
878
  </span>
769
- </div>
770
- </div>
771
- <div className="flex items-center gap-0.5">
879
+ </span>
880
+ </FormatHeader>
881
+ <div className="flex items-center gap-0.5 pr-2.5">
772
882
  <Tooltip
773
883
  content={
774
884
  option.canDownload
@@ -888,9 +998,7 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
888
998
  type="button"
889
999
  size="xs"
890
1000
  variant="outline"
891
- onClick={() =>
892
- setMetadataRefresh((current) => current + 1)
893
- }
1001
+ onClick={metadataRequest.refetch}
894
1002
  >
895
1003
  Retry
896
1004
  </Button>
@@ -906,17 +1014,13 @@ export const ExportActions: React.FC<ExportActionProps> = (props) => {
906
1014
  missingPackageFailure?.description ??
907
1015
  eligibility?.reason
908
1016
  }
909
- onInstall={() => {
910
- void retryAfterInstall(
911
- missingPackageFailure?.action ?? {
912
- destination: "download",
913
- format:
914
- option.format === "geojson"
915
- ? "geojson"
916
- : "parquet",
917
- },
918
- );
919
- }}
1017
+ onInstall={createInstallRetry(
1018
+ missingPackageFailure?.action ?? {
1019
+ destination: "download",
1020
+ format:
1021
+ option.format === "geojson" ? "geojson" : "parquet",
1022
+ },
1023
+ )}
920
1024
  className="items-start"
921
1025
  />
922
1026
  </div>
@@ -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 };
@@ -3,42 +3,6 @@
3
3
  import z from "zod";
4
4
  import { rpc } from "@/plugins/core/rpc";
5
5
 
6
- export type DownloadFormat = "csv" | "json" | "parquet" | "tsv" | "geojson";
7
-
8
- /**
9
- * Per-request export options. Each field is optional. A missing field keeps
10
- * the widget's default for that setting.
11
- */
12
- export interface DownloadAsOptions {
13
- separator?: string;
14
- encoding?: string;
15
- ensure_ascii?: boolean;
16
- }
17
-
18
- export interface DownloadAsRequest {
19
- format: DownloadFormat;
20
- options?: DownloadAsOptions;
21
- geometry_column?: string | null;
22
- }
23
-
24
- export type DownloadAsArgs = (req: DownloadAsRequest) => Promise<{
25
- url: string;
26
- filename: string;
27
- error?: string | null;
28
- missing_packages?: string[] | null;
29
- code?:
30
- | "geometry_required"
31
- | "invalid_geometry"
32
- | "invalid_metadata"
33
- | "unsupported_representation"
34
- | "unsupported_version"
35
- | "missing_packages"
36
- | "missing_crs"
37
- | "conversion_failed"
38
- | null;
39
- column?: string | null;
40
- }>;
41
-
42
6
  const DownloadRequestSchema = z.object({
43
7
  geometry_column: z.string().nullish(),
44
8
  options: z
@@ -88,6 +52,20 @@ export const DownloadGeoJSONSchema = rpc
88
52
  }),
89
53
  );
90
54
 
55
+ export type DownloadAsRequest =
56
+ | z.infer<typeof DownloadAsSchema.input>
57
+ | z.infer<typeof DownloadGeoJSONSchema.input>;
58
+
59
+ type DownloadResponse =
60
+ | z.infer<typeof DownloadAsSchema.output>
61
+ | z.infer<typeof DownloadGeoJSONSchema.output>;
62
+
63
+ export type DownloadFormat = DownloadAsRequest["format"];
64
+ export type DownloadAsOptions = NonNullable<DownloadAsRequest["options"]>;
65
+ export type DownloadAsArgs = (
66
+ req: DownloadAsRequest,
67
+ ) => Promise<DownloadResponse>;
68
+
91
69
  export type DownloadAsFunction = (
92
70
  req: z.infer<typeof DownloadAsSchema.input>,
93
71
  ) => Promise<z.infer<typeof DownloadAsSchema.output>>;