@marimo-team/islands 0.25.3-dev14 → 0.25.3-dev140

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 (453) hide show
  1. package/dist/{ConnectedDataExplorerComponent-CNMliVzw.js → ConnectedDataExplorerComponent-BAiSVaHj.js} +17 -15
  2. package/dist/{ErrorBoundary-S_edj6VQ.js → ErrorBoundary-CTJ1U1fx.js} +16 -16
  3. package/dist/{abnfDiagram-VCTEODGH-D-Ez91gV.js → abnfDiagram-VCTEODGH-BBHN92-F.js} +2 -2
  4. package/dist/any-language-editor-3E24SnCV.js +75 -0
  5. package/dist/{architectureDiagram-5GKGNRK7-CTpcNtO8.js → architectureDiagram-5GKGNRK7-C0Rd64qh.js} +1 -1
  6. package/dist/{arrays-CN1djC4k.js → arrays-R6JsImiT.js} +8 -0
  7. package/dist/assets/__vite-browser-external-BgQNn4dS.js +1 -0
  8. package/dist/assets/worker-FKe3RHT4.js +113 -0
  9. package/dist/{button-CvjkwdSk.js → button-BQRdqQi1.js} +199 -192
  10. package/dist/{capabilities-CQ5IjhzL.js → capabilities-CI4xUPsp.js} +2 -2
  11. package/dist/{chat-ui-DaHynk8g.js → chat-ui-CvB8Map3.js} +1142 -1141
  12. package/dist/{check-EDwSwP89.js → check-SpOMoCLP.js} +1 -1
  13. package/dist/{chunk-IMKFNOWR-DGxi-zeo.js → chunk-IMKFNOWR-hJXey5p2.js} +1 -1
  14. package/dist/{chunk-SHT3W25Y-CV9J3rAB.js → chunk-SHT3W25Y-JMzTmmVW.js} +1 -1
  15. package/dist/{chunk-SVP7TREG-BL-9Fqgg.js → chunk-SVP7TREG-BIyoHTXi.js} +1 -1
  16. package/dist/{chunk-TICWLB2K-Cu_XMjDM.js → chunk-TICWLB2K-CCzH2Uwa.js} +1 -1
  17. package/dist/{chunk-YOKDWASO-DPZk8vhS.js → chunk-YOKDWASO-DKplTrBY.js} +20 -20
  18. package/dist/{classDiagram-ZZMXUADV-BXXV13Hq.js → classDiagram-ZZMXUADV-DB1kOPS6.js} +2 -2
  19. package/dist/{classDiagram-v2-VYDZK3BY-B7FXJ1Cp.js → classDiagram-v2-VYDZK3BY-CPaFL0OB.js} +2 -2
  20. package/dist/{code-visibility-BBdICLTP.js → code-visibility-C1jZfwPW.js} +6 -6
  21. package/dist/{common-7r9HNiTn.js → common-CNv_7gvI.js} +6766 -6590
  22. package/dist/{copy-icon-Ft7t9bqz.js → copy-icon-C1trdvVt.js} +3 -3
  23. package/dist/{cynefinDiagram-5FMLGOSQ-CH96rwba.js → cynefinDiagram-5FMLGOSQ-CF93uliH.js} +1 -1
  24. package/dist/{dagre-GXQ25YYZ-BD3PmpFY.js → dagre-GXQ25YYZ-BBB-_p9D.js} +1 -1
  25. package/dist/{data-grid-overlay-editor-Bz1FTAfW.js → data-grid-overlay-editor-t0t17DPI.js} +5 -5
  26. package/dist/{data-grid-types-BH_r7g7l.js → data-grid-types-0PJ-9N5n.js} +1 -1
  27. package/dist/{diagram-S7CK7UJ4-BsSJJldB.js → diagram-S7CK7UJ4-BynCnbyt.js} +1 -1
  28. package/dist/{diagram-UQ7AKVKN-muQtNZc4.js → diagram-UQ7AKVKN-svOxikMa.js} +1 -1
  29. package/dist/{diagram-VX7I27RA-CdhuZAqR.js → diagram-VX7I27RA-BVQSnPx0.js} +1 -1
  30. package/dist/{diagram-Z3DM3KII-BD8mXjOj.js → diagram-Z3DM3KII-nR1J0BH5.js} +1 -1
  31. package/dist/{dialog-CEBFZNyc.js → dialog-DgeqyrEZ.js} +3 -3
  32. package/dist/{dist-DpBpA_n1.js → dist-Lm-D-Vr6.js} +3 -3
  33. package/dist/{dropdown-menu-CP_qCYHC.js → dropdown-menu-GWcGCcxE.js} +1277 -1229
  34. package/dist/{ebnfDiagram-PWID7BFC-BIN1sweE.js → ebnfDiagram-PWID7BFC-C9tq9-Qo.js} +2 -2
  35. package/dist/{erDiagram-RLTQ6QDP-Ctp5cfoN.js → erDiagram-RLTQ6QDP-BiaeB7Fi.js} +1 -1
  36. package/dist/{error-banner-D-WCIiMt.js → error-banner-D40pmfQQ.js} +83 -83
  37. package/dist/{error-entries-Bv6bZPF3.js → error-entries-CmFDQ9CA.js} +1018 -977
  38. package/dist/{feedback-button-BAheGhdl.js → feedback-button-JzaiyZZm.js} +23 -23
  39. package/dist/{flowDiagram-HODETNUW-3EfLrlFb.js → flowDiagram-HODETNUW-f-sLm87k.js} +2 -2
  40. package/dist/{formats-27CW_UP4.js → formats-CCxaLPNs.js} +3 -3
  41. package/dist/{ganttDiagram-EL5Y4UJY-BQT16JSc.js → ganttDiagram-EL5Y4UJY-Dh_4cBJZ.js} +2 -2
  42. package/dist/{glide-data-editor-CAvrTnyB.js → glide-data-editor-B3eq-1SC.js} +370 -368
  43. package/dist/{glide-utils-DJyqh0EM.js → glide-utils-Dz_F4pAY.js} +16 -16
  44. package/dist/{globals-C03jB5fY.js → globals-BW8TOLYQ.js} +3 -3
  45. package/dist/{highlighted-body-KPVGNVTW-zwjq21tf.js → highlighted-body-KPVGNVTW-3Ziwfz-V.js} +2 -2
  46. package/dist/{html-to-image-Bf8QMGi4.js → html-to-image-BrC8OJcj.js} +632 -595
  47. package/dist/{infoDiagram-27XIBGKW-DipQoHxl.js → infoDiagram-27XIBGKW-BHrWywBe.js} +1 -1
  48. package/dist/{ishikawaDiagram-5VMMS53U-DgFMjSGz.js → ishikawaDiagram-5VMMS53U-Da4s0Reg.js} +1 -1
  49. package/dist/{kanban-definition-UXKFOSKX-w1ACVjgl.js → kanban-definition-UXKFOSKX-DDf1GVLj.js} +1 -1
  50. package/dist/{label-BDKF3Afc.js → label-D9_-zSg-.js} +3 -3
  51. package/dist/{linear-Cnaq3Tfb.js → linear-DWERh7eI.js} +121 -73
  52. package/dist/{loader-BQOfjTIJ.js → loader-DOVB0Syl.js} +12 -12
  53. package/dist/main.js +7551 -7231
  54. package/dist/{mermaid-CHo49Yv-.js → mermaid-ClwTt7OD.js} +38 -38
  55. package/dist/mermaid-HWGCJPDP-Du3-HPQA.js +2 -0
  56. package/dist/{mindmap-definition-YA3MSWOX-CrT0KSfX.js → mindmap-definition-YA3MSWOX-D60oMZtT.js} +1 -1
  57. package/dist/{number-overlay-editor-DRF7iVLO.js → number-overlay-editor-DoUnDbPE.js} +3 -3
  58. package/dist/{objects-CzUgoMIF.js → objects-DCmMvZtn.js} +1 -3
  59. package/dist/{once-BtE-wKRL.js → once-eG8Vmvjd.js} +1 -1
  60. package/dist/{pegDiagram-XKGWAZYB-BqvywNJA.js → pegDiagram-XKGWAZYB-B9wFpMga.js} +2 -2
  61. package/dist/{pieDiagram-E7YTZNPT-BfxJ_XCc.js → pieDiagram-E7YTZNPT-BZwvMuBT.js} +1 -1
  62. package/dist/precisionRound-DhAzeRcm.js +49 -0
  63. package/dist/{process-output-BZg56CCb.js → process-output-Bw-vzyoy.js} +5 -5
  64. package/dist/{quadrantDiagram-AXDQQJYC-Ccw0xMjN.js → quadrantDiagram-AXDQQJYC-Blrm7V7_.js} +1 -1
  65. package/dist/{railroadDiagram-O6MQD6OU-8vFLVTPu.js → railroadDiagram-O6MQD6OU-DqhZGsN2.js} +2 -2
  66. package/dist/{react-vega-BED0DjiB.js → react-vega-C4T9Rb_w.js} +12284 -12270
  67. package/dist/{requirementDiagram-BXWQKSXE-a_MxGvxe.js → requirementDiagram-BXWQKSXE-CL5dYARV.js} +1 -1
  68. package/dist/{reveal-component-QSvE6jwa.js → reveal-component-ClNn1CDl.js} +14 -14
  69. package/dist/{select-BButFi-d.js → select-CkF_GBxO.js} +537 -458
  70. package/dist/{sequenceDiagram-WJ2MYXX4-CXFMnxGh.js → sequenceDiagram-WJ2MYXX4-BSUCRd2x.js} +1 -1
  71. package/dist/{spec-BwarBpUJ.js → spec-DiSh_qJC.js} +41 -11
  72. package/dist/{stateDiagram-D77RDMKH-DQeA_CwI.js → stateDiagram-D77RDMKH-B-1Hvyb0.js} +2 -2
  73. package/dist/{stateDiagram-v2-MP3YSRHH-BxxQucm1.js → stateDiagram-v2-MP3YSRHH--HtiR4yB.js} +2 -2
  74. package/dist/style.css +1 -1
  75. package/dist/{swimlanes-42K2YHIH-Do6L-cYg.js → swimlanes-42K2YHIH-Bdfhrd-2.js} +1 -1
  76. package/dist/{swimlanesDiagram-VR7AAH4N-DUzPvSVU.js → swimlanesDiagram-VR7AAH4N-BJIfxSz0.js} +2 -2
  77. package/dist/{swiper-component-DKntyn8p.js → swiper-component-BkCm-kLg.js} +2 -2
  78. package/dist/{time-BieKwdYS.js → time-BaMbJs4z.js} +18 -18
  79. package/dist/{timeline-definition-24CTP7MA-D3c46WM5.js → timeline-definition-24CTP7MA-Cs4EfFD3.js} +1 -1
  80. package/dist/{toDate-UtiwmnH2.js → toDate-DGvRHwTk.js} +19 -18
  81. package/dist/{toast-B9UMEi8u.js → toast-Btbve3Lm.js} +231 -213
  82. package/dist/{tooltip-BO1REbqs.js → tooltip-CJ4Rqasp.js} +327 -303
  83. package/dist/{use-restore-focus-BEM7sFWl.js → use-restore-focus-CmGwRNId.js} +1482 -1433
  84. package/dist/{useAsyncData-Deh5kszT.js → useAsyncData-BmVgnMP5.js} +15 -14
  85. package/dist/{useDateFormatter-D8XruNGG.js → useDateFormatter-eCL_SdeG.js} +2 -2
  86. package/dist/{useDeepCompareMemoize-DXwzRINI.js → useDeepCompareMemoize-Ch_AeGu6.js} +1 -1
  87. package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities-CSdWn6NP.js} +1 -1
  88. package/dist/{useLifecycle-j7D9uD1o.js → useLifecycle-B5BX921H.js} +3 -3
  89. package/dist/{useTheme-CRnjkku0.js → useTheme-Bq9pi_vz.js} +6 -6
  90. package/dist/{vega-component-BeF9dfyO.js → vega-component-BgyZ-COt.js} +147 -147
  91. package/dist/{vega-embed-container-BNHE18G9.js → vega-embed-container--LYZqguO.js} +2 -2
  92. package/dist/{vega-loader.browser-DNST6uVf.js → vega-loader.browser-B9Pz0EGP.js} +298 -507
  93. package/dist/{vennDiagram-4TSXK5OY-BSZD8JcZ.js → vennDiagram-4TSXK5OY-DRu0vRjO.js} +1 -1
  94. package/dist/{wardleyDiagram-VM6X3IG4-48cUWkfA.js → wardleyDiagram-VM6X3IG4-DzbDxwDY.js} +1 -1
  95. package/dist/{xychartDiagram-S5SC5T6Z-CswYgYeu.js → xychartDiagram-S5SC5T6Z-CLWj6ZFQ.js} +2 -2
  96. package/package.json +14 -14
  97. package/src/__tests__/host-imports.test.ts +1 -0
  98. package/src/__tests__/module-entry.test.ts +32 -0
  99. package/src/__tests__/mount.test.ts +0 -20
  100. package/src/components/ai/__tests__/ai-model-dropdown.test.tsx +63 -0
  101. package/src/components/ai/ai-model-dropdown.tsx +11 -6
  102. package/src/components/app-config/ai-config.tsx +7 -2
  103. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  104. package/src/components/chat/acp/__tests__/session-tabs.test.tsx +43 -0
  105. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  106. package/src/components/chat/acp/blocks.tsx +3 -2
  107. package/src/components/chat/acp/session-tabs.tsx +8 -3
  108. package/src/components/chat/chat-components.tsx +2 -0
  109. package/src/components/chat/chat-panel.tsx +9 -8
  110. package/src/components/chat/chat-utils.ts +4 -8
  111. package/src/components/data-table/TableTopBar.tsx +2 -0
  112. package/src/components/data-table/__tests__/chart-spec-model.test.ts +195 -195
  113. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  114. package/src/components/data-table/__tests__/columns.test.tsx +105 -0
  115. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  116. package/src/components/data-table/__tests__/data-table.test.tsx +1 -0
  117. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  118. package/src/components/data-table/__tests__/export-actions.test.tsx +105 -0
  119. package/src/components/data-table/__tests__/keyboard-selection.test.tsx +188 -0
  120. package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
  121. package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
  122. package/src/components/data-table/cell-selection/feature.ts +2 -2
  123. package/src/components/data-table/cell-selection/types.ts +2 -2
  124. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
  125. package/src/components/data-table/charts/__tests__/spec-snapshot.test.ts +20 -20
  126. package/src/components/data-table/charts/__tests__/spec.test.ts +78 -52
  127. package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
  128. package/src/components/data-table/charts/chart-spec/spec.ts +68 -43
  129. package/src/components/data-table/charts/charts.tsx +40 -30
  130. package/src/components/data-table/charts/storage.ts +16 -5
  131. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  132. package/src/components/data-table/column-summary/chart-spec-model.tsx +23 -16
  133. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  134. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  135. package/src/components/data-table/columns.tsx +45 -46
  136. package/src/components/data-table/context-menu.tsx +36 -20
  137. package/src/components/data-table/data-table.tsx +13 -1
  138. package/src/components/data-table/date-popover.tsx +6 -4
  139. package/src/components/data-table/export-actions.tsx +76 -48
  140. package/src/components/data-table/range-focus/atoms.ts +4 -6
  141. package/src/components/data-table/range-focus/use-cell-range-selection.ts +65 -38
  142. package/src/components/data-table/range-focus/use-scroll-into-view.ts +21 -2
  143. package/src/components/data-table/renderers.tsx +16 -7
  144. package/src/components/data-table/row-viewer-panel/__tests__/row-viewer.test.tsx +24 -1
  145. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +4 -4
  146. package/src/components/data-table/search-highlight.tsx +79 -0
  147. package/src/components/data-table/types.ts +1 -0
  148. package/src/components/data-table/url-detector.tsx +49 -9
  149. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  150. package/src/components/datasources/__tests__/column-preview.test.tsx +98 -5
  151. package/src/components/datasources/__tests__/datasources-interactions.test.tsx +181 -0
  152. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  153. package/src/components/datasources/__tests__/utils.test.ts +57 -6
  154. package/src/components/datasources/column-preview.tsx +13 -4
  155. package/src/components/datasources/datasources.tsx +51 -39
  156. package/src/components/datasources/utils.ts +32 -23
  157. package/src/components/debugger/debugger-code.tsx +5 -0
  158. package/src/components/dependency-graph/dependency-graph-tree.tsx +8 -8
  159. package/src/components/dependency-graph/elements.ts +24 -21
  160. package/src/components/dependency-graph/panels.tsx +2 -1
  161. package/src/components/dependency-graph/utils/__tests__/layout.test.ts +18 -11
  162. package/src/components/editor/SortableCell.tsx +1 -3
  163. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  164. package/src/components/editor/__tests__/package-alert.test.tsx +22 -1
  165. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +24 -0
  166. package/src/components/editor/actions/pair-with-agent-modal.tsx +18 -15
  167. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  168. package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
  169. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  170. package/src/components/editor/cell/CreateCellButton.tsx +8 -10
  171. package/src/components/editor/cell/DeleteButton.tsx +1 -0
  172. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  173. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  174. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  175. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  176. package/src/components/editor/cell/collapse.tsx +5 -3
  177. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  178. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
  179. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  180. package/src/components/editor/chrome/panels/outline/__tests__/outline-keyboard.test.tsx +84 -0
  181. package/src/components/editor/chrome/panels/outline/floating-outline.tsx +39 -14
  182. package/src/components/editor/chrome/panels/outline/useActiveOutline.tsx +3 -2
  183. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  184. package/src/components/editor/chrome/panels/secrets-panel.tsx +1 -0
  185. package/src/components/editor/chrome/panels/snippets-panel.tsx +5 -1
  186. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +12 -18
  187. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +58 -1
  188. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  189. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
  190. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  191. package/src/components/editor/connections/components.tsx +1 -1
  192. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  193. package/src/components/editor/controls/__tests__/keyboard-shortcuts.test.tsx +61 -0
  194. package/src/components/editor/controls/command-palette.tsx +22 -10
  195. package/src/components/editor/controls/keyboard-shortcuts.tsx +14 -5
  196. package/src/components/editor/dynamic-favicon.tsx +21 -40
  197. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +12 -23
  198. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  199. package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +47 -0
  200. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  201. package/src/components/editor/file-tree/file-explorer.tsx +45 -8
  202. package/src/components/editor/file-tree/file-name-input.tsx +1 -0
  203. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  204. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  205. package/src/components/editor/header/status.tsx +28 -5
  206. package/src/components/editor/links/__tests__/cell-link.test.tsx +146 -0
  207. package/src/components/editor/links/cell-link.tsx +14 -5
  208. package/src/components/editor/navigation/focus-utils.ts +4 -4
  209. package/src/components/editor/notebook-banner.tsx +1 -0
  210. package/src/components/editor/notebook-cell.tsx +11 -2
  211. package/src/components/editor/output/MarimoTracebackOutput.tsx +19 -17
  212. package/src/components/editor/output/__tests__/traceback.test.tsx +27 -1
  213. package/src/components/editor/output/ansi-reduce.ts +33 -20
  214. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  215. package/src/components/editor/package-alert.tsx +14 -7
  216. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +12 -6
  217. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  218. package/src/components/home/components.tsx +17 -31
  219. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  220. package/src/components/icons/large-spinner.tsx +3 -14
  221. package/src/components/icons/spinner.tsx +1 -1
  222. package/src/components/mcp/mcp-status-indicator.tsx +1 -1
  223. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  224. package/src/components/pages/home-page.tsx +164 -91
  225. package/src/components/scratchpad/scratchpad.test.tsx +95 -0
  226. package/src/components/scratchpad/scratchpad.tsx +16 -3
  227. package/src/components/slides/reveal-component.tsx +4 -2
  228. package/src/components/slides/slide-cell-view.tsx +1 -1
  229. package/src/components/slides/slide-form.tsx +1 -0
  230. package/src/components/tracing/tracing-keyboard.test.tsx +85 -0
  231. package/src/components/tracing/tracing.tsx +45 -41
  232. package/src/components/ui/__tests__/draggable-popover.test.tsx +24 -0
  233. package/src/components/ui/__tests__/range-slider.test.tsx +165 -0
  234. package/src/components/ui/alert-dialog.tsx +1 -1
  235. package/src/components/ui/draggable-popover.tsx +24 -2
  236. package/src/components/ui/range-slider.tsx +22 -9
  237. package/src/core/ai/context/providers/datasource.ts +6 -2
  238. package/src/core/ai/tools/base.ts +16 -7
  239. package/src/core/ai/tools/edit-notebook-tool.ts +47 -45
  240. package/src/core/ai/tools/registry.ts +13 -8
  241. package/src/core/ai/tools/sample-tool.ts +6 -7
  242. package/src/core/attachments/state.ts +5 -8
  243. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -0
  244. package/src/core/cells/__tests__/cell.test.ts +39 -0
  245. package/src/core/cells/__tests__/cells.test.ts +3 -1
  246. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
  247. package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
  248. package/src/core/cells/__tests__/session.test.ts +117 -36
  249. package/src/core/cells/cell.ts +14 -3
  250. package/src/core/cells/cells.ts +3 -1
  251. package/src/core/cells/pending-delete-service.ts +2 -6
  252. package/src/core/cells/session.ts +1 -1
  253. package/src/core/cells/types.ts +10 -1
  254. package/src/core/codemirror/__tests__/setup.test.ts +29 -29
  255. package/src/core/codemirror/cells/__tests__/extensions.test.ts +15 -0
  256. package/src/core/codemirror/cells/extensions.ts +7 -3
  257. package/src/core/codemirror/keymaps/vim.ts +2 -6
  258. package/src/core/codemirror/language/__tests__/sql.test.ts +22 -20
  259. package/src/core/codemirror/language/extension.ts +8 -8
  260. package/src/core/codemirror/language/languages/markdown.ts +8 -6
  261. package/src/core/codemirror/language/languages/python.ts +16 -10
  262. package/src/core/codemirror/language/languages/sql/sql.ts +9 -7
  263. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  264. package/src/core/codemirror/language/types.ts +7 -7
  265. package/src/core/codemirror/rtc/extension.ts +24 -12
  266. package/src/core/codemirror/rtc/loro/awareness.ts +34 -19
  267. package/src/core/codemirror/rtc/loro/sync.ts +1 -0
  268. package/src/core/codemirror/utils.ts +1 -0
  269. package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
  270. package/src/core/datasets/data-source-discovery.ts +4 -6
  271. package/src/core/document/__tests__/codemirror.test.ts +485 -0
  272. package/src/core/document/__tests__/draft.test.ts +322 -0
  273. package/src/core/document/__tests__/fixtures.ts +92 -0
  274. package/src/core/document/__tests__/hooks.test.tsx +386 -0
  275. package/src/core/document/__tests__/lifecycle-scenarios.test.ts +61 -0
  276. package/src/core/document/__tests__/model.test.ts +191 -0
  277. package/src/core/document/__tests__/protocol-fixtures.ts +38 -0
  278. package/src/core/document/__tests__/recovery-scenarios.test.ts +230 -0
  279. package/src/core/document/__tests__/scenario-fixture.ts +43 -0
  280. package/src/core/document/__tests__/scenarios.test.ts +205 -0
  281. package/src/core/document/__tests__/state.test.ts +438 -0
  282. package/src/core/document/__tests__/submission.test.ts +203 -0
  283. package/src/core/document/__tests__/test-host.test.ts +92 -0
  284. package/src/core/document/__tests__/test-host.ts +247 -0
  285. package/src/core/document/__tests__/test-notebook.ts +149 -0
  286. package/src/core/document/__tests__/user.ts +61 -0
  287. package/src/core/document/codemirror.ts +311 -0
  288. package/src/core/document/draft.ts +472 -0
  289. package/src/core/document/hooks.tsx +118 -0
  290. package/src/core/document/index.ts +15 -0
  291. package/src/core/document/model.ts +55 -0
  292. package/src/core/document/select.ts +41 -0
  293. package/src/core/document/state.ts +374 -0
  294. package/src/core/document/sync.ts +369 -0
  295. package/src/core/host/AGENTS.md +8 -2
  296. package/src/core/host/__tests__/attachments.test.ts +56 -0
  297. package/src/core/host/__tests__/document.test.ts +126 -0
  298. package/src/core/host/__tests__/fixtures.ts +71 -0
  299. package/src/core/host/__tests__/replica.test.ts +550 -0
  300. package/src/core/host/__tests__/static.test.ts +169 -0
  301. package/src/core/host/__tests__/wasm.test.ts +163 -0
  302. package/src/core/host/backend.ts +19 -0
  303. package/src/core/host/connection.ts +1 -40
  304. package/src/core/host/index.ts +14 -0
  305. package/src/core/host/replica.ts +286 -0
  306. package/src/core/host/resources/attachments.ts +22 -0
  307. package/src/core/host/resources/document.ts +101 -0
  308. package/src/core/host/static.ts +72 -0
  309. package/src/core/host/wasm.ts +107 -0
  310. package/src/core/hotkeys/actions.ts +2 -4
  311. package/src/core/islands/__tests__/bootstrap.test.ts +16 -42
  312. package/src/core/islands/__tests__/bridge.test.ts +31 -26
  313. package/src/core/islands/bootstrap.ts +22 -52
  314. package/src/core/islands/bridge.ts +31 -20
  315. package/src/core/islands/components/__tests__/IslandControls.test.tsx +7 -5
  316. package/src/core/islands/worker/__tests__/controller.test.ts +62 -4
  317. package/src/core/islands/worker/controller.ts +4 -10
  318. package/src/core/islands/worker/worker.tsx +33 -33
  319. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  320. package/src/core/network/connection.ts +4 -2
  321. package/src/core/network/useConnectionNotice.ts +8 -2
  322. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  323. package/src/core/runtime/__tests__/runtime.test.ts +32 -58
  324. package/src/core/runtime/runtime.ts +38 -42
  325. package/src/core/runtime/types.ts +0 -2
  326. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  327. package/src/core/state/jotai.ts +1 -1
  328. package/src/core/static/__tests__/files.test.ts +38 -10
  329. package/src/core/static/files.ts +2 -0
  330. package/src/core/wasm/__tests__/bridge.test.ts +42 -1
  331. package/src/core/wasm/__tests__/worker.test.ts +182 -0
  332. package/src/core/wasm/bridge.ts +40 -21
  333. package/src/core/wasm/rpc.ts +0 -1
  334. package/src/core/wasm/store.ts +3 -3
  335. package/src/core/wasm/worker/bootstrap.ts +55 -51
  336. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  337. package/src/core/wasm/worker/types.ts +23 -14
  338. package/src/core/wasm/worker/worker.ts +77 -36
  339. package/src/core/websocket/__tests__/backend.test.ts +228 -0
  340. package/src/core/websocket/__tests__/connection-state.test.ts +0 -12
  341. package/src/core/websocket/__tests__/useNotebookReplica.hook.test.tsx +141 -0
  342. package/src/core/websocket/__tests__/useNotebookSession.hook.test.tsx +154 -428
  343. package/src/core/websocket/backend.ts +130 -0
  344. package/src/core/websocket/connection-state.ts +1 -11
  345. package/src/core/websocket/state.ts +7 -0
  346. package/src/core/websocket/transports/__tests__/notebook.test.ts +405 -0
  347. package/src/core/websocket/transports/notebook.ts +159 -0
  348. package/src/core/websocket/types.ts +2 -2
  349. package/src/core/websocket/useNotebookReplica.ts +69 -0
  350. package/src/core/websocket/useNotebookSession.tsx +18 -25
  351. package/src/css/globals.css +5 -0
  352. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  353. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  354. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  355. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  356. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  357. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  358. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  359. package/src/hooks/useAsyncData.ts +3 -3
  360. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  361. package/src/hooks/useDebounce.ts +6 -10
  362. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  363. package/src/hooks/useDelayElapsed.ts +14 -10
  364. package/src/hooks/useElapsedTime.ts +3 -3
  365. package/src/hooks/useEventListener.ts +1 -2
  366. package/src/hooks/useInputHistory.ts +4 -7
  367. package/src/hooks/useInternalStateWithSync.ts +6 -6
  368. package/src/hooks/useResizeObserver.ts +4 -7
  369. package/src/hooks/useScript.ts +67 -70
  370. package/src/hooks/useStateForKey.ts +93 -0
  371. package/src/mount.tsx +0 -4
  372. package/src/plugins/__tests__/plugin-object-schemas.test.ts +104 -0
  373. package/src/plugins/core/registerReactComponent.tsx +1 -1
  374. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  375. package/src/plugins/impl/DataEditorPlugin.tsx +1 -1
  376. package/src/plugins/impl/DataTablePlugin.tsx +29 -33
  377. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  378. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  379. package/src/plugins/impl/FormPlugin.tsx +1 -1
  380. package/src/plugins/impl/RangeSliderPlugin.tsx +16 -2
  381. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  382. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  383. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +117 -1
  384. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  385. package/src/plugins/impl/__tests__/RangeSliderPlugin.test.tsx +109 -0
  386. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  387. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
  388. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  389. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  390. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  391. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  392. package/src/plugins/impl/chat/chat-ui.tsx +14 -11
  393. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  394. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  395. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  396. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  397. package/src/plugins/impl/data-explorer/components/column-summary.tsx +4 -2
  398. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  399. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -2
  400. package/src/plugins/impl/data-frames/__tests__/sidebar.test.tsx +160 -0
  401. package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +66 -66
  402. package/src/plugins/impl/data-frames/panel.tsx +43 -16
  403. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  404. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  405. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  406. package/src/plugins/impl/panel/utils.ts +17 -0
  407. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +2 -5
  408. package/src/plugins/impl/plotly/selection.ts +25 -19
  409. package/src/plugins/impl/vega/VegaPlugin.tsx +2 -3
  410. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  411. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  412. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  413. package/src/stories/button.stories.tsx +1 -0
  414. package/src/stories/editor.stories.tsx +24 -17
  415. package/src/stories/form-wrapper.stories.tsx +2 -2
  416. package/src/stories/select.stories.tsx +2 -2
  417. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  418. package/src/utils/__tests__/arrays.test.ts +35 -0
  419. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  420. package/src/utils/__tests__/numbers.test.ts +105 -1
  421. package/src/utils/__tests__/objects.test.ts +21 -0
  422. package/src/utils/arrays.ts +10 -0
  423. package/src/utils/blob.ts +1 -1
  424. package/src/utils/numbers.ts +79 -18
  425. package/src/utils/objects.ts +3 -7
  426. package/src/utils/once.ts +19 -19
  427. package/src/utils/tracer.ts +9 -5
  428. package/src/utils/zod-utils.ts +1 -3
  429. package/dist/any-language-editor-DZ_YTVus.js +0 -76
  430. package/dist/assets/__vite-browser-external-BXYkiFsk.js +0 -1
  431. package/dist/assets/worker-HPY9fbhz.js +0 -114
  432. package/dist/mermaid-HWGCJPDP-ByCsZnYF.js +0 -2
  433. package/dist/precisionRound-DMIVQrt2.js +0 -97
  434. package/src/core/host/__tests__/events.test.ts +0 -46
  435. package/src/core/host/__tests__/notebook-client.test.ts +0 -191
  436. package/src/core/host/__tests__/protocol.test.ts +0 -214
  437. package/src/core/host/events.ts +0 -60
  438. package/src/core/host/notebook-client.ts +0 -226
  439. package/src/core/host/protocol.ts +0 -1943
  440. package/src/core/state/__tests__/jotai.test.ts +0 -25
  441. package/src/core/wasm/worker/message-buffer.ts +0 -33
  442. package/src/core/websocket/__tests__/createConnectionTransport.test.ts +0 -49
  443. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +0 -140
  444. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  445. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -335
  446. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  447. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  448. package/src/core/websocket/transports/basic.ts +0 -102
  449. package/src/core/websocket/transports/create.ts +0 -31
  450. package/src/core/websocket/transports/sse.ts +0 -308
  451. package/src/core/websocket/transports/transport.ts +0 -50
  452. package/src/core/websocket/transports/ws.ts +0 -98
  453. package/src/core/websocket/useNotebookClient.ts +0 -80
@@ -119,6 +119,7 @@ const RefreshComponent = ({ setValue, data }: IPluginProps<Value, Data>) => {
119
119
  <Labeled label={data.label}>
120
120
  <span className="inline-flex items-center text-secondary-foreground rounded shadow-sm-solid">
121
121
  <Button
122
+ aria-label="Refresh"
122
123
  data-testid="marimo-plugin-refresh-button"
123
124
  variant="secondary"
124
125
  size="icon"
@@ -1,8 +1,9 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- import { type JSX, useEffect, useId, useMemo, useState } from "react";
2
+ import { type JSX, useId, useMemo } from "react";
3
3
  import { useLocale } from "react-aria";
4
4
  import { z } from "zod";
5
5
  import { NumberField } from "@/components/ui/number-field";
6
+ import { useInternalStateWithSync } from "@/hooks/useInternalStateWithSync";
6
7
  import { cn } from "@/utils/cn";
7
8
  import { clamp } from "@/utils/math";
8
9
  import {
@@ -156,13 +157,10 @@ const SliderComponent = ({
156
157
  const { locale } = useLocale();
157
158
 
158
159
  // Hold internal value
159
- const [internalValue, setInternalValue] = useState(value);
160
- // Update internal value on prop change
161
- useEffect(() => {
162
- // TODO: Replace this synchronization with a controlled-state primitive.
163
- // oxlint-disable-next-line react/no-deriving-state-in-effects -- Synchronize the editable buffer when the controlled value changes.
164
- setInternalValue(value);
165
- }, [value]);
160
+ const [internalValue, setInternalValue] = useInternalStateWithSync(
161
+ value,
162
+ Object.is,
163
+ );
166
164
 
167
165
  const stepsConfig = useMemo(() => computeStepsConfig(steps), [steps]);
168
166
 
@@ -12,11 +12,13 @@ import {
12
12
  waitFor,
13
13
  } from "@testing-library/react";
14
14
  import { Provider } from "jotai";
15
+ import { useState } from "react";
15
16
  import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
16
17
  import { SetupMocks } from "@/__mocks__/common";
17
18
  import type { DownloadAsFunction } from "@/components/data-table/schemas";
18
19
  import type { FieldTypesWithExternalType } from "@/components/data-table/types";
19
20
  import { store } from "@/core/state/jotai";
21
+ import { Deferred } from "@/utils/Deferred";
20
22
  import {
21
23
  type GetDataUrl,
22
24
  type GetRowIds,
@@ -463,7 +465,7 @@ describe("LoadingDataTableComponent", () => {
463
465
  });
464
466
  });
465
467
 
466
- describe("static notebook control suppression", () => {
468
+ describe("table pagination and static notebook control suppression", () => {
467
469
  const fieldTypes: FieldTypesWithExternalType = [
468
470
  ["id", ["integer", "integer"]],
469
471
  ["name", ["string", "string"]],
@@ -538,6 +540,120 @@ describe("static notebook control suppression", () => {
538
540
  mockIsStatic.mockReturnValue(false);
539
541
  });
540
542
 
543
+ it("requests only the first page with the new configured page size", async () => {
544
+ const props = makeProps();
545
+ const view = (pageSize: number) => (
546
+ <Wrapper>
547
+ <LoadingDataTableComponent {...props} pageSize={pageSize} />
548
+ </Wrapper>
549
+ );
550
+ const { rerender } = render(view(PAGE_SIZE));
551
+ await waitFor(() =>
552
+ expect(screen.getByTestId("next-page-button")).toBeEnabled(),
553
+ );
554
+ fireEvent.click(screen.getByTestId("next-page-button"));
555
+ await waitFor(() =>
556
+ expect(props.search).toHaveBeenLastCalledWith(
557
+ expect.objectContaining({ page_number: 1, page_size: PAGE_SIZE }),
558
+ ),
559
+ );
560
+ await waitFor(() =>
561
+ expect(screen.getByTestId("next-page-button")).toBeEnabled(),
562
+ );
563
+ props.search.mockClear();
564
+
565
+ rerender(view(5));
566
+ await waitFor(() => expect(props.search).toHaveBeenCalled());
567
+ expect(
568
+ props.search.mock.calls.every(
569
+ ([request]) => request.page_number === 0 && request.page_size === 5,
570
+ ),
571
+ ).toBe(true);
572
+ });
573
+
574
+ it("preserves a new page size when a pending response changes the row count in the same batch", async () => {
575
+ const props = makeProps();
576
+ const Harness = () => {
577
+ const [pageSize, setPageSize] = useState(PAGE_SIZE);
578
+ return (
579
+ <Wrapper>
580
+ <button type="button" onClick={() => setPageSize(5)}>
581
+ Change configured page size
582
+ </button>
583
+ <LoadingDataTableComponent {...props} pageSize={pageSize} />
584
+ </Wrapper>
585
+ );
586
+ };
587
+ render(<Harness />);
588
+ await waitFor(() =>
589
+ expect(screen.getByTestId("next-page-button")).toBeEnabled(),
590
+ );
591
+ const response = {
592
+ data: firstPage,
593
+ total_rows: 25,
594
+ cell_styles: null,
595
+ cell_hover_texts: null,
596
+ };
597
+ const pendingPage = new Deferred<typeof response>();
598
+ props.search.mockReturnValueOnce(pendingPage.promise);
599
+ fireEvent.click(screen.getByTestId("next-page-button"));
600
+ await waitFor(() =>
601
+ expect(props.search).toHaveBeenLastCalledWith(
602
+ expect.objectContaining({ page_number: 1, page_size: PAGE_SIZE }),
603
+ ),
604
+ );
605
+ props.search.mockClear();
606
+
607
+ await act(async () => {
608
+ pendingPage.resolve(response);
609
+ // Settle the table-loading promises without committing the queued render.
610
+ await new Promise((resolve) => setTimeout(resolve, 0));
611
+ fireEvent.click(
612
+ screen.getByRole("button", { name: "Change configured page size" }),
613
+ );
614
+ });
615
+
616
+ await waitFor(() => expect(props.search).toHaveBeenCalled());
617
+ expect(props.search).toHaveBeenLastCalledWith(
618
+ expect.objectContaining({ page_number: 0, page_size: 5 }),
619
+ );
620
+ });
621
+
622
+ it("returns to the first page when a refreshed result changes the row count", async () => {
623
+ const props = makeProps();
624
+ const view = () => (
625
+ <Wrapper>
626
+ <LoadingDataTableComponent {...props} />
627
+ </Wrapper>
628
+ );
629
+ const { rerender } = render(view());
630
+ await waitFor(() =>
631
+ expect(screen.getByTestId("next-page-button")).toBeEnabled(),
632
+ );
633
+ fireEvent.click(screen.getByTestId("next-page-button"));
634
+ await waitFor(() =>
635
+ expect(props.search).toHaveBeenLastCalledWith(
636
+ expect.objectContaining({ page_number: 1 }),
637
+ ),
638
+ );
639
+ await waitFor(() =>
640
+ expect(screen.getByTestId("next-page-button")).toBeEnabled(),
641
+ );
642
+ props.search = vi.fn().mockResolvedValue({
643
+ data: firstPage,
644
+ total_rows: 25,
645
+ cell_styles: null,
646
+ cell_hover_texts: null,
647
+ });
648
+
649
+ rerender(view());
650
+ await waitFor(() =>
651
+ expect(props.search).toHaveBeenLastCalledWith(
652
+ expect.objectContaining({ page_number: 0, page_size: PAGE_SIZE }),
653
+ ),
654
+ );
655
+ });
656
+
541
657
  it("renders kernel-dependent controls in normal mode", async () => {
542
658
  const props = makeProps();
543
659
  render(
@@ -1,6 +1,7 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import {
4
+ act,
4
5
  fireEvent,
5
6
  render,
6
7
  screen,
@@ -88,6 +89,50 @@ beforeAll(() => {
88
89
  });
89
90
 
90
91
  describe("FileBrowserPlugin session cache recovery", () => {
92
+ it("delays the initial loading overlay and hides it when listing completes", async () => {
93
+ vi.useFakeTimers();
94
+ try {
95
+ let resolveListing: (value: {
96
+ files: MockFile[];
97
+ total_count: number;
98
+ is_truncated: boolean;
99
+ }) => void = () => undefined;
100
+ const listDirectory = vi.fn(
101
+ () =>
102
+ new Promise((resolve) => {
103
+ resolveListing = resolve;
104
+ }),
105
+ );
106
+ const { unmount } = renderBrowser({ list_directory: listDirectory });
107
+
108
+ await act(async () => {
109
+ vi.advanceTimersByTime(199);
110
+ });
111
+ expect(screen.queryByText("Listing files...")).not.toBeInTheDocument();
112
+ await act(async () => {
113
+ vi.advanceTimersByTime(1);
114
+ });
115
+ expect(screen.getByText("Listing files...")).toBeInTheDocument();
116
+
117
+ await act(async () => {
118
+ resolveListing({
119
+ files: FILES,
120
+ total_count: FILES.length,
121
+ is_truncated: false,
122
+ });
123
+ });
124
+ expect(screen.queryByText("Listing files...")).not.toBeInTheDocument();
125
+ expect(screen.getByText("docs")).toBeInTheDocument();
126
+ await act(async () => {
127
+ vi.advanceTimersByTime(200);
128
+ });
129
+ expect(screen.queryByText("Listing files...")).not.toBeInTheDocument();
130
+ unmount();
131
+ } finally {
132
+ vi.useRealTimers();
133
+ }
134
+ });
135
+
91
136
  it("retries a failed cached listing when the plugin resets", async () => {
92
137
  const host = document.createElement("marimo-file-browser");
93
138
  const wrapper = document.createElement("marimo-ui-element");
@@ -0,0 +1,109 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { render } from "@testing-library/react";
4
+ import { describe, expect, it, vi } from "vitest";
5
+ import type { z } from "zod";
6
+ import type { IPluginProps } from "../../types";
7
+ import { RangeSliderPlugin } from "../RangeSliderPlugin";
8
+
9
+ vi.mock("@/components/ui/range-slider", () => ({
10
+ RangeSlider: () => <div data-testid="range-slider" />,
11
+ }));
12
+
13
+ vi.mock("react-aria", () => ({
14
+ useLocale: () => ({ locale: "en-US" }),
15
+ }));
16
+
17
+ describe("RangeSliderPlugin", () => {
18
+ type PluginProps = IPluginProps<
19
+ number[],
20
+ z.infer<typeof RangeSliderPlugin.prototype.validator>
21
+ >;
22
+
23
+ const createProps = (
24
+ value: number[],
25
+ data: Partial<PluginProps["data"]> = {},
26
+ ): PluginProps => ({
27
+ host: document.createElement("div"),
28
+ value,
29
+ setValue: vi.fn(),
30
+ data: {
31
+ initialValue: value,
32
+ label: null,
33
+ start: 0,
34
+ stop: 10,
35
+ step: 0.01,
36
+ steps: null,
37
+ debounce: false,
38
+ orientation: "horizontal",
39
+ showValue: true,
40
+ fullWidth: true,
41
+ disabled: false,
42
+ ...data,
43
+ },
44
+ functions: {},
45
+ });
46
+
47
+ it("rounds floating-point artifacts in the displayed range", () => {
48
+ const plugin = new RangeSliderPlugin();
49
+ const props = createProps([1.2, 1.500000000000001]);
50
+
51
+ const { getByText } = render(plugin.render(props));
52
+
53
+ expect(getByText("1.2, 1.5")).toBeDefined();
54
+ });
55
+
56
+ it("preserves precision from finer-grained steps", () => {
57
+ const plugin = new RangeSliderPlugin();
58
+ const props = createProps([1.234, 1.235000000000001], { step: 0.001 });
59
+
60
+ const { getByText } = render(plugin.render(props));
61
+
62
+ expect(getByText("1.234, 1.235")).toBeDefined();
63
+ });
64
+
65
+ it("retains value precision when no step is supplied", () => {
66
+ const plugin = new RangeSliderPlugin();
67
+ const props = createProps([1.2345, 1.500000000000001], {
68
+ step: undefined,
69
+ });
70
+
71
+ const { getByText } = render(plugin.render(props));
72
+
73
+ expect(getByText("1.2345, 1.5")).toBeDefined();
74
+ });
75
+
76
+ it("preserves precision for small displayed values", () => {
77
+ const plugin = new RangeSliderPlugin();
78
+ const props = createProps([0.00123, 0.0012400000000000002], {
79
+ stop: 0.01,
80
+ step: 0.00001,
81
+ });
82
+
83
+ const { getByText } = render(plugin.render(props));
84
+
85
+ expect(getByText("1.23e-3, 1.24e-3")).toBeDefined();
86
+ });
87
+
88
+ it("infers precision from custom steps", () => {
89
+ const plugin = new RangeSliderPlugin();
90
+ const props = createProps([0, 1], {
91
+ stop: 2,
92
+ step: undefined,
93
+ steps: [1, 1.234, 2],
94
+ });
95
+
96
+ const { getByText } = render(plugin.render(props));
97
+
98
+ expect(getByText("1, 1.234")).toBeDefined();
99
+ });
100
+
101
+ it("preserves decimals from the slider origin", () => {
102
+ const plugin = new RangeSliderPlugin();
103
+ const props = createProps([0.5, 1.5], { start: 0.5, stop: 10, step: 1 });
104
+
105
+ const { getByText } = render(plugin.render(props));
106
+
107
+ expect(getByText("0.5, 1.5")).toBeDefined();
108
+ });
109
+ });
@@ -212,6 +212,28 @@ describe("SliderPlugin", () => {
212
212
  expect(setValue).toHaveBeenCalledWith(9);
213
213
  });
214
214
 
215
+ it("preserves an uncommitted drag until the controlled value changes", () => {
216
+ const plugin = new SliderPlugin();
217
+ const setValue = vi.fn();
218
+ const props = createProps({
219
+ debounce: true,
220
+ includeInput: false,
221
+ setValue,
222
+ });
223
+ const { getByRole, rerender } = render(plugin.render(props));
224
+
225
+ fireEvent.click(getByRole("button", { name: "Slider change" }));
226
+ rerender(
227
+ plugin.render({ ...props, data: { ...props.data, label: "Updated" } }),
228
+ );
229
+ fireEvent.click(getByRole("button", { name: "Slider commit" }));
230
+ expect(setValue).toHaveBeenLastCalledWith(6);
231
+
232
+ rerender(plugin.render({ ...props, value: 2 }));
233
+ fireEvent.click(getByRole("button", { name: "Slider commit" }));
234
+ expect(setValue).toHaveBeenLastCalledWith(2);
235
+ });
236
+
215
237
  describe("editable input with steps (regression for #9850)", () => {
216
238
  it("displays the actual step value, not the index", () => {
217
239
  const plugin = new SliderPlugin();
@@ -381,9 +403,8 @@ describe("SliderPlugin", () => {
381
403
  vi.advanceTimersByTime(0);
382
404
  });
383
405
 
384
- // Shrink `steps` so the held index (4) is now out of range. The index
385
- // syncs in an effect after this render, so the render must not throw on
386
- // the stale, out-of-range index.
406
+ // Shrink `steps` so the held index (4) is now out of range. Updating
407
+ // the controlled index must render the new valid step without throwing.
387
408
  const shrunkProps = createStepsProps({
388
409
  steps: [10, 20],
389
410
  valueIndex: 1,
@@ -238,6 +238,7 @@ describe("WidgetRegistry.getWidget", () => {
238
238
  jsUrl: SPEC.url,
239
239
  jsHash: SPEC.hash,
240
240
  kernelAuthored: true,
241
+ lifetime: expect.any(AbortSignal),
241
242
  });
242
243
  expect(widgetDef.initialize).toHaveBeenCalledTimes(1);
243
244
  expect(widget.exports).toBe(exports);
@@ -263,6 +264,7 @@ describe("WidgetRegistry.getWidget", () => {
263
264
  jsUrl: SPEC.url,
264
265
  jsHash: SPEC.hash,
265
266
  kernelAuthored: true,
267
+ lifetime: expect.any(AbortSignal),
266
268
  });
267
269
  });
268
270
 
@@ -7,9 +7,12 @@ import { userConfigAtom } from "@/core/config/config";
7
7
  import { parseUserConfig } from "@/core/config/config-schema";
8
8
  import { initialModeAtom } from "@/core/mode";
9
9
  import { store } from "@/core/state/jotai";
10
+ import type { DataURLString } from "@/utils/json/base64";
10
11
  import { Model } from "../model";
11
12
  import type { Host } from "../host";
12
- import type { ModelState } from "../types";
13
+ import type { ModelState, WidgetModelId } from "../types";
14
+ import { WidgetRuntime } from "../runtime";
15
+ import { Deferred } from "@/utils/Deferred";
13
16
  import { visibleForTesting } from "../widget-binding";
14
17
 
15
18
  const { WidgetDefRegistry, WidgetBinding } = visibleForTesting;
@@ -30,13 +33,14 @@ const createTestHost = (): Host => ({
30
33
  function createBinding<T extends ModelState>(
31
34
  widgetDef: AnyWidget<T>,
32
35
  model: Model<T>,
33
- options: { controller?: AbortController } = {},
36
+ options: { controller?: AbortController; onDestroy?: () => void } = {},
34
37
  ) {
35
38
  return WidgetBinding.create({
36
39
  widgetDef,
37
40
  model,
38
41
  createHost: createTestHost,
39
42
  controller: options.controller,
43
+ onDestroy: options.onDestroy,
40
44
  });
41
45
  }
42
46
 
@@ -123,6 +127,25 @@ describe("WidgetDefRegistry", () => {
123
127
  promise2.catch(() => undefined);
124
128
  });
125
129
 
130
+ it("does not evict a replacement when an invalidated import fails", async () => {
131
+ const oldImport = getModule(
132
+ registry,
133
+ "http://localhost/old.js",
134
+ "replaced",
135
+ );
136
+ registry.invalidate("replaced");
137
+ const newImport = registry.getModule({
138
+ jsUrl: "data:text/javascript,export default {}",
139
+ jsHash: "replaced",
140
+ kernelAuthored: true,
141
+ });
142
+ await expect(oldImport).rejects.toThrow(/untrusted/i);
143
+ await newImport;
144
+ expect(getModule(registry, "http://localhost/new.js", "replaced")).toBe(
145
+ newImport,
146
+ );
147
+ });
148
+
126
149
  describe("URL validation", () => {
127
150
  it.each([
128
151
  // Attack vector: raw <marimo-anywidget data-js-url=...> in markdown
@@ -148,9 +171,225 @@ describe("WidgetDefRegistry", () => {
148
171
  ).rejects.not.toThrow(/untrusted/i);
149
172
  });
150
173
  });
174
+
175
+ describe("static module URLs", () => {
176
+ afterEach(() => {
177
+ delete window.__MARIMO_STATIC__;
178
+ vi.restoreAllMocks();
179
+ });
180
+
181
+ it("releases the generated URL when a static module fails to import", async () => {
182
+ window.__MARIMO_STATIC__ = {
183
+ files: {
184
+ "/@file/widget.js":
185
+ "data:text/javascript;base64,ZXhwb3J0IHt9" as DataURLString,
186
+ },
187
+ };
188
+ const create = vi
189
+ .spyOn(URL, "createObjectURL")
190
+ .mockReturnValue("blob:widget-module");
191
+ const revoke = vi
192
+ .spyOn(URL, "revokeObjectURL")
193
+ .mockImplementation(() => undefined);
194
+
195
+ await expect(
196
+ getModule(registry, "./@file/widget.js", "static-module"),
197
+ ).rejects.toThrow(/blob:/);
198
+
199
+ expect(create).toHaveBeenCalledOnce();
200
+ expect(revoke).toHaveBeenCalledExactlyOnceWith("blob:widget-module");
201
+ });
202
+
203
+ it("does not revoke active modules when their cache entry is invalidated", async () => {
204
+ window.__MARIMO_STATIC__ = {
205
+ files: {
206
+ "/@file/widget.js":
207
+ "data:text/javascript;base64,ZXhwb3J0IHt9" as DataURLString,
208
+ },
209
+ };
210
+ const oldUrl = "data:text/javascript,export const version = 1";
211
+ const newUrl = "data:text/javascript,export const version = 2";
212
+ vi.spyOn(URL, "createObjectURL")
213
+ .mockReturnValueOnce(oldUrl)
214
+ .mockReturnValueOnce(newUrl);
215
+ const revoke = vi
216
+ .spyOn(URL, "revokeObjectURL")
217
+ .mockImplementation(() => undefined);
218
+ const oldLifetime = new AbortController();
219
+ const newLifetime = new AbortController();
220
+ const options = { jsUrl: "./@file/widget.js", jsHash: "invalidated" };
221
+ const oldImport = registry.getModule({
222
+ ...options,
223
+ lifetime: oldLifetime.signal,
224
+ });
225
+ registry.invalidate(options.jsHash);
226
+ const newImport = registry.getModule({
227
+ ...options,
228
+ lifetime: newLifetime.signal,
229
+ });
230
+ await Promise.all([oldImport, newImport]);
231
+ expect(revoke).not.toHaveBeenCalled();
232
+ oldLifetime.abort();
233
+ await vi.waitFor(() =>
234
+ expect(revoke).toHaveBeenCalledExactlyOnceWith(oldUrl),
235
+ );
236
+ const siblingLifetime = new AbortController();
237
+ expect(
238
+ registry.getModule({ ...options, lifetime: siblingLifetime.signal }),
239
+ ).toBe(newImport);
240
+ newLifetime.abort();
241
+ siblingLifetime.abort();
242
+ await vi.waitFor(() => expect(revoke).toHaveBeenLastCalledWith(newUrl));
243
+ });
244
+
245
+ it("waits for a canceled import to settle before revoking its URL", async () => {
246
+ window.__MARIMO_STATIC__ = {
247
+ files: {
248
+ "/@file/widget.js":
249
+ "data:text/javascript;base64,ZXhwb3J0IHt9" as DataURLString,
250
+ },
251
+ };
252
+ const moduleUrl = "data:text/javascript,export const pending = true";
253
+ vi.spyOn(URL, "createObjectURL").mockReturnValue(moduleUrl);
254
+ const revoke = vi
255
+ .spyOn(URL, "revokeObjectURL")
256
+ .mockImplementation(() => undefined);
257
+ const lifetime = new AbortController();
258
+ const pending = registry.getModule({
259
+ jsUrl: "./@file/widget.js",
260
+ jsHash: "pending",
261
+ lifetime: lifetime.signal,
262
+ });
263
+ lifetime.abort();
264
+ expect(revoke).not.toHaveBeenCalled();
265
+ await pending;
266
+ expect(revoke).toHaveBeenCalledExactlyOnceWith(moduleUrl);
267
+ });
268
+
269
+ it("does not revoke an unchanged URL for a missing static file", async () => {
270
+ window.__MARIMO_STATIC__ = { files: {} };
271
+ const revoke = vi
272
+ .spyOn(URL, "revokeObjectURL")
273
+ .mockImplementation(() => undefined);
274
+
275
+ await expect(
276
+ getModule(registry, "./@file/missing.js", "missing-module"),
277
+ ).rejects.toThrow(/http:/);
278
+
279
+ expect(revoke).not.toHaveBeenCalled();
280
+ });
281
+
282
+ it("keeps a shared module URL until the last widget generation is destroyed", async () => {
283
+ window.__MARIMO_STATIC__ = {
284
+ files: {
285
+ "/@file/widget.js":
286
+ "data:text/javascript;base64,ZXhwb3J0IHt9" as DataURLString,
287
+ },
288
+ };
289
+ // Node cannot import blob URLs; a data URL exercises successful loading.
290
+ const moduleUrl = `data:text/javascript,${encodeURIComponent(
291
+ "export default { initialize: () => ({ moduleUrl: import.meta.url }) }",
292
+ )}`;
293
+ vi.spyOn(URL, "createObjectURL").mockReturnValue(moduleUrl);
294
+ const revoke = vi
295
+ .spyOn(URL, "revokeObjectURL")
296
+ .mockImplementation(() => undefined);
297
+
298
+ const createRuntime = (id: string) => {
299
+ const runtime = new WidgetRuntime(id as WidgetModelId, {
300
+ timeout: 50,
301
+ isEditMode: () => true,
302
+ createHost: createTestHost,
303
+ onModelTimeout: vi.fn(),
304
+ });
305
+ runtime.setModel(new Model({}, createMockComm()));
306
+ runtime.setSpec({ url: "./@file/widget.js", hash: "static-success" });
307
+ return runtime;
308
+ };
309
+ const first = createRuntime("first");
310
+ const second = createRuntime("second");
311
+ const widgets = await Promise.all([
312
+ first.getWidget(),
313
+ second.getWidget(),
314
+ ]);
315
+ expect(widgets.map((widget) => widget.exports)).toEqual([
316
+ { moduleUrl },
317
+ { moduleUrl },
318
+ ]);
319
+ expect(URL.createObjectURL).toHaveBeenCalledOnce();
320
+ expect(revoke).not.toHaveBeenCalled();
321
+
322
+ first.dispose();
323
+ await new Promise((resolve) => setTimeout(resolve, 0));
324
+ expect(revoke).not.toHaveBeenCalled();
325
+
326
+ second.dispose();
327
+ await vi.waitFor(() => {
328
+ expect(revoke).toHaveBeenCalledExactlyOnceWith(moduleUrl);
329
+ });
330
+
331
+ // A later generation reuses the evaluated module rather than
332
+ // re-running its top-level side effects, and does not revoke again.
333
+ const third = createRuntime("third");
334
+ const widget = await third.getWidget();
335
+ expect(widget.exports).toEqual({ moduleUrl });
336
+ expect(URL.createObjectURL).toHaveBeenCalledOnce();
337
+ third.dispose();
338
+ await new Promise((resolve) => setTimeout(resolve, 0));
339
+ expect(revoke).toHaveBeenCalledOnce();
340
+ });
341
+ });
151
342
  });
152
343
 
153
344
  describe("WidgetBinding", () => {
345
+ it("releases module resources after asynchronous generation cleanup", async () => {
346
+ const cleanup = new Deferred<void>();
347
+ const onDestroy = vi.fn();
348
+ const binding = await createBinding(
349
+ { initialize: () => () => cleanup.promise },
350
+ new Model({}, createMockComm()),
351
+ { onDestroy },
352
+ );
353
+ const destroying = binding.destroy();
354
+ expect(onDestroy).not.toHaveBeenCalled();
355
+ cleanup.resolve();
356
+ await destroying;
357
+ await binding.destroy();
358
+ expect(onDestroy).toHaveBeenCalledOnce();
359
+ });
360
+
361
+ it("retains module resources until canceled initialization and late cleanup settle", async () => {
362
+ const initialize = new Deferred<() => Promise<void>>();
363
+ const cleanup = new Deferred<void>();
364
+ const controller = new AbortController();
365
+ const onDestroy = vi.fn();
366
+ const pending = createBinding(
367
+ { initialize: () => initialize.promise },
368
+ new Model({}, createMockComm()),
369
+ { controller, onDestroy },
370
+ );
371
+ controller.abort();
372
+ await expect(pending).rejects.toThrow(/binding destroyed/);
373
+ expect(onDestroy).not.toHaveBeenCalled();
374
+ initialize.resolve(() => cleanup.promise);
375
+ await Promise.resolve();
376
+ expect(onDestroy).not.toHaveBeenCalled();
377
+ cleanup.resolve();
378
+ await vi.waitFor(() => expect(onDestroy).toHaveBeenCalledOnce());
379
+ });
380
+
381
+ it("releases module resources when initialization fails", async () => {
382
+ const onDestroy = vi.fn();
383
+ await expect(
384
+ createBinding(
385
+ { initialize: () => Promise.reject(new Error("initialize failed")) },
386
+ new Model({}, createMockComm()),
387
+ { onDestroy },
388
+ ),
389
+ ).rejects.toThrow("initialize failed");
390
+ await vi.waitFor(() => expect(onDestroy).toHaveBeenCalledOnce());
391
+ });
392
+
154
393
  it("should run initialize exactly once and expose exports", async () => {
155
394
  const model = new Model<ModelState>({ count: 0 }, createMockComm());
156
395
  const exports = { setValue: vi.fn() };