@marimo-team/islands 0.25.3-dev19 → 0.25.3-dev191

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 (627) hide show
  1. package/dist/{ConnectedDataExplorerComponent-CNMliVzw.js → ConnectedDataExplorerComponent-BBbOElk3.js} +21 -19
  2. package/dist/{ErrorBoundary-CBHB755J.js → ErrorBoundary--X0z83Yx.js} +19 -19
  3. package/dist/{abnfDiagram-VCTEODGH-D-Ez91gV.js → abnfDiagram-VCTEODGH-DhMT6Uhd.js} +2 -2
  4. package/dist/any-language-editor-BVFHDDQ3.js +75 -0
  5. package/dist/{architectureDiagram-5GKGNRK7-CTpcNtO8.js → architectureDiagram-5GKGNRK7-BVbEiJLq.js} +1 -1
  6. package/dist/{arrays-CN1djC4k.js → arrays-R6JsImiT.js} +8 -0
  7. package/dist/assets/__vite-browser-external-C8gQho07.js +1 -0
  8. package/dist/assets/worker-C6e9ERyd.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-BguxTQEL.js} +541 -540
  12. package/dist/{check-EDwSwP89.js → check-C9Mp9eCK.js} +1 -1
  13. package/dist/{chunk-IMKFNOWR-DGxi-zeo.js → chunk-IMKFNOWR-By5hSbJJ.js} +1 -1
  14. package/dist/{chunk-SHT3W25Y-CV9J3rAB.js → chunk-SHT3W25Y-DFrEYG54.js} +1 -1
  15. package/dist/{chunk-SVP7TREG-BL-9Fqgg.js → chunk-SVP7TREG-DFXGc9ew.js} +1 -1
  16. package/dist/{chunk-TICWLB2K-Cu_XMjDM.js → chunk-TICWLB2K-DZdNIpoZ.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-Cgj_eEQA.js} +2 -2
  19. package/dist/{classDiagram-v2-VYDZK3BY-B7FXJ1Cp.js → classDiagram-v2-VYDZK3BY-DLAUrSqj.js} +2 -2
  20. package/dist/{code-visibility-CEXXuuOE.js → code-visibility-uDyLo9_i.js} +25 -25
  21. package/dist/{common-OLE92qsR.js → common-DWKY45Md.js} +6994 -6819
  22. package/dist/{copy-icon-Ft7t9bqz.js → copy-icon-De584pZ4.js} +3 -3
  23. package/dist/{cynefinDiagram-5FMLGOSQ-CH96rwba.js → cynefinDiagram-5FMLGOSQ-Cikf9AKa.js} +1 -1
  24. package/dist/{dagre-GXQ25YYZ-BD3PmpFY.js → dagre-GXQ25YYZ-C5KEi5_l.js} +1 -1
  25. package/dist/{data-grid-overlay-editor-CpHX98zN.js → data-grid-overlay-editor-CRnYWD1L.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-BaoBTQjR.js} +1 -1
  28. package/dist/{diagram-UQ7AKVKN-muQtNZc4.js → diagram-UQ7AKVKN-DDufBsUA.js} +1 -1
  29. package/dist/{diagram-VX7I27RA-CdhuZAqR.js → diagram-VX7I27RA-CY6RALJ8.js} +1 -1
  30. package/dist/{diagram-Z3DM3KII-BD8mXjOj.js → diagram-Z3DM3KII-Bc-LHsD6.js} +1 -1
  31. package/dist/{dialog-CEBFZNyc.js → dialog-DVRfQuhD.js} +25 -25
  32. package/dist/{dist-F8Ep36rk.js → dist-B1M0bGO3.js} +1 -1
  33. package/dist/{dist-D6d86eNJ.js → dist-BA13RJj5.js} +1 -1
  34. package/dist/{dist-Bvs24R_E.js → dist-BJtmSgcE.js} +48 -48
  35. package/dist/{dist-CN_BeG2I.js → dist-Ba2VIn1K.js} +53 -53
  36. package/dist/{dist-PXYI_yuP.js → dist-Bj4bmwUm.js} +12 -12
  37. package/dist/{dist-DoE4XJBC.js → dist-BmS-ZGjl.js} +8626 -8626
  38. package/dist/{dist-BorMD-uX.js → dist-Bzogh7CW.js} +87 -87
  39. package/dist/{dist-CN8zI-XC.js → dist-CThPVKaP.js} +1385 -1382
  40. package/dist/{dist-DiDibOYr.js → dist-CTi5FTiV.js} +87 -87
  41. package/dist/{dist-diIx5qJW.js → dist-Ck_C32ye.js} +2 -2
  42. package/dist/{dist-BLfKmR-l.js → dist-D1fnFp1J.js} +1 -1
  43. package/dist/{dist-OR1OnN-y.js → dist-D3x9__Fu.js} +46 -46
  44. package/dist/{dist-BMQf5zIS.js → dist-D7mrh5sO.js} +36 -36
  45. package/dist/{dist-CSJ0MDRw.js → dist-D9Q3xjiP.js} +1 -1
  46. package/dist/{dist-BuaRHxOc.js → dist-DP60Ubh8.js} +2 -2
  47. package/dist/{dist-CSbh1PNP.js → dist-DTv9KkpW.js} +12 -12
  48. package/dist/{dist-D-wwqlzJ.js → dist-DVVwOfrD.js} +109 -109
  49. package/dist/{dist-C8fAKSH1.js → dist-DYXM73-k.js} +36 -36
  50. package/dist/{dist-DBtSl3sf.js → dist-DjeybPOb.js} +1 -1
  51. package/dist/{dist-CrSP_H1M.js → dist-DtGn9N4h.js} +197 -197
  52. package/dist/{dist-CLdpGQVr.js → dist-KN80qWCP.js} +2 -2
  53. package/dist/{dist-DpBpA_n1.js → dist-Lm-D-Vr6.js} +3 -3
  54. package/dist/{dist-Cg7gzw-a.js → dist-O0qFpEJh.js} +56 -56
  55. package/dist/{dist-CkTTIdBe.js → dist-OUle2RT7.js} +207 -207
  56. package/dist/{dist-BK4Oj99o.js → dist-T5rh6JvL.js} +1 -1
  57. package/dist/{dropdown-menu-CP_qCYHC.js → dropdown-menu-drDjxJyV.js} +1481 -1433
  58. package/dist/{ebnfDiagram-PWID7BFC-BIN1sweE.js → ebnfDiagram-PWID7BFC-Cz0J-tcC.js} +2 -2
  59. package/dist/{erDiagram-RLTQ6QDP-Ctp5cfoN.js → erDiagram-RLTQ6QDP-DTXTDgFu.js} +1 -1
  60. package/dist/{error-banner-D-WCIiMt.js → error-banner-DxJ3s8YO.js} +86 -86
  61. package/dist/{error-entries-Bv6bZPF3.js → error-entries-CZ1NQ6Kv.js} +4771 -4702
  62. package/dist/{esm-D2gTsXEV.js → esm-Bbw_Njjx.js} +575 -575
  63. package/dist/{esm-AMFssoNp.js → esm-mDQNEFe_.js} +354 -354
  64. package/dist/{feedback-button-_f7fx7zj.js → feedback-button-CDf1yRm_.js} +37 -37
  65. package/dist/{flowDiagram-HODETNUW-3EfLrlFb.js → flowDiagram-HODETNUW-RbwWf2zc.js} +2 -2
  66. package/dist/{formats-27CW_UP4.js → formats-DbXnijtH.js} +3 -3
  67. package/dist/{ganttDiagram-EL5Y4UJY-BQT16JSc.js → ganttDiagram-EL5Y4UJY-Dh_4cBJZ.js} +2 -2
  68. package/dist/{glide-data-editor-Cmu4vWKD.js → glide-data-editor-CvRiA49N.js} +370 -368
  69. package/dist/{glide-utils-DJyqh0EM.js → glide-utils-D98laidb.js} +16 -16
  70. package/dist/{globals-1Hfhz6Dx.js → globals-BR4CmGOS.js} +3 -3
  71. package/dist/{highlighted-body-KPVGNVTW-zwjq21tf.js → highlighted-body-KPVGNVTW-3Ziwfz-V.js} +2 -2
  72. package/dist/{html-to-image-Bf8QMGi4.js → html-to-image-D_4_hQ8U.js} +668 -622
  73. package/dist/{infoDiagram-27XIBGKW-DipQoHxl.js → infoDiagram-27XIBGKW-DlmzZjmI.js} +1 -1
  74. package/dist/{ishikawaDiagram-5VMMS53U-DgFMjSGz.js → ishikawaDiagram-5VMMS53U-EN3TYP_q.js} +1 -1
  75. package/dist/{kanban-definition-UXKFOSKX-w1ACVjgl.js → kanban-definition-UXKFOSKX-CK_-Ru7Q.js} +1 -1
  76. package/dist/{label-BDKF3Afc.js → label-Qvw1E97W.js} +3 -3
  77. package/dist/{linear-Cnaq3Tfb.js → linear-DWERh7eI.js} +121 -73
  78. package/dist/{loader-BQOfjTIJ.js → loader-DOVB0Syl.js} +12 -12
  79. package/dist/main.js +9822 -7824
  80. package/dist/{mermaid-CHo49Yv-.js → mermaid-DU9ZRmkG.js} +38 -38
  81. package/dist/mermaid-HWGCJPDP-Du3-HPQA.js +2 -0
  82. package/dist/{mindmap-definition-YA3MSWOX-CrT0KSfX.js → mindmap-definition-YA3MSWOX-C-hqgUvF.js} +1 -1
  83. package/dist/{number-overlay-editor-BQN3-Nye.js → number-overlay-editor-DiYGbxNa.js} +3 -3
  84. package/dist/{objects-CzUgoMIF.js → objects-DCmMvZtn.js} +1 -3
  85. package/dist/{once-BtE-wKRL.js → once-eG8Vmvjd.js} +1 -1
  86. package/dist/{pegDiagram-XKGWAZYB-BqvywNJA.js → pegDiagram-XKGWAZYB-BiMejYPB.js} +2 -2
  87. package/dist/{pieDiagram-E7YTZNPT-BfxJ_XCc.js → pieDiagram-E7YTZNPT-B1zCZ1eq.js} +1 -1
  88. package/dist/precisionRound-DhAzeRcm.js +49 -0
  89. package/dist/{process-output-BZg56CCb.js → process-output-DxSCHBWp.js} +4 -4
  90. package/dist/{quadrantDiagram-AXDQQJYC-Ccw0xMjN.js → quadrantDiagram-AXDQQJYC-Blrm7V7_.js} +1 -1
  91. package/dist/{railroadDiagram-O6MQD6OU-8vFLVTPu.js → railroadDiagram-O6MQD6OU-BLcfaASl.js} +2 -2
  92. package/dist/{react-vega-BED0DjiB.js → react-vega-C4T9Rb_w.js} +12284 -12270
  93. package/dist/{requirementDiagram-BXWQKSXE-a_MxGvxe.js → requirementDiagram-BXWQKSXE-DtBOf52-.js} +1 -1
  94. package/dist/{reveal-component-nqUW7zgn.js → reveal-component-D3DsXPIm.js} +734 -711
  95. package/dist/{select-BButFi-d.js → select-2KOiReNY.js} +536 -457
  96. package/dist/{sequenceDiagram-WJ2MYXX4-CXFMnxGh.js → sequenceDiagram-WJ2MYXX4-DIwF19_n.js} +1 -1
  97. package/dist/{spec-BwarBpUJ.js → spec-DZDnnlgT.js} +41 -11
  98. package/dist/{stateDiagram-D77RDMKH-DQeA_CwI.js → stateDiagram-D77RDMKH-sWk1AdYL.js} +2 -2
  99. package/dist/{stateDiagram-v2-MP3YSRHH-BxxQucm1.js → stateDiagram-v2-MP3YSRHH-CiNo-OI7.js} +2 -2
  100. package/dist/style.css +1 -1
  101. package/dist/{swimlanes-42K2YHIH-Do6L-cYg.js → swimlanes-42K2YHIH-Bj3hiOY5.js} +1 -1
  102. package/dist/{swimlanesDiagram-VR7AAH4N-DUzPvSVU.js → swimlanesDiagram-VR7AAH4N-BzEo2r_N.js} +2 -2
  103. package/dist/{swiper-component-DKntyn8p.js → swiper-component-BkCm-kLg.js} +2 -2
  104. package/dist/{time-BieKwdYS.js → time-BaMbJs4z.js} +18 -18
  105. package/dist/{timeline-definition-24CTP7MA-D3c46WM5.js → timeline-definition-24CTP7MA-CrPQk42H.js} +1 -1
  106. package/dist/{toDate-UtiwmnH2.js → toDate-Cu2pIZcC.js} +37 -36
  107. package/dist/{toast-B9UMEi8u.js → toast-B5t9o6s_.js} +219 -201
  108. package/dist/{tooltip-BO1REbqs.js → tooltip-s4zZxwKe.js} +325 -301
  109. package/dist/{use-restore-focus-BEM7sFWl.js → use-restore-focus-BgDTd2tc.js} +1482 -1433
  110. package/dist/{useAsyncData-Deh5kszT.js → useAsyncData-DEhLdRIu.js} +15 -14
  111. package/dist/{useDateFormatter-D8XruNGG.js → useDateFormatter-D3Tkfzej.js} +2 -2
  112. package/dist/{useDeepCompareMemoize-DXwzRINI.js → useDeepCompareMemoize-RjZGVhBV.js} +1 -1
  113. package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities-CSdWn6NP.js} +1 -1
  114. package/dist/{useLifecycle-j7D9uD1o.js → useLifecycle-DtcB2s1z.js} +28 -25
  115. package/dist/{useTheme-CRnjkku0.js → useTheme-CoEtr4mK.js} +301 -297
  116. package/dist/{vega-component-BeF9dfyO.js → vega-component-BhjTrP9-.js} +148 -148
  117. package/dist/{vega-embed-container-BNHE18G9.js → vega-embed-container--LYZqguO.js} +2 -2
  118. package/dist/{vega-loader.browser-DNST6uVf.js → vega-loader.browser-B9Pz0EGP.js} +298 -507
  119. package/dist/{vennDiagram-4TSXK5OY-BSZD8JcZ.js → vennDiagram-4TSXK5OY-BaYMoIpj.js} +1 -1
  120. package/dist/{wardleyDiagram-VM6X3IG4-48cUWkfA.js → wardleyDiagram-VM6X3IG4-DjHeuj8p.js} +1 -1
  121. package/dist/{xychartDiagram-S5SC5T6Z-CswYgYeu.js → xychartDiagram-S5SC5T6Z-Bc_hcuXS.js} +2 -2
  122. package/package.json +16 -16
  123. package/src/__mocks__/document.tsx +54 -0
  124. package/src/__mocks__/notebook.ts +2 -1
  125. package/src/__mocks__/requests.ts +0 -3
  126. package/src/__tests__/module-entry.test.ts +44 -0
  127. package/src/__tests__/mount.test.ts +1 -21
  128. package/src/__tests__/{host-imports.test.ts → replica-imports.test.ts} +12 -11
  129. package/src/components/ai/__tests__/ai-model-dropdown.test.tsx +63 -0
  130. package/src/components/ai/ai-model-dropdown.tsx +11 -6
  131. package/src/components/app-config/ai-config.tsx +7 -2
  132. package/src/components/app-config/user-config-form.tsx +35 -0
  133. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  134. package/src/components/chat/__tests__/notebook-access-selector.test.tsx +64 -0
  135. package/src/components/chat/acp/__tests__/session-tabs.test.tsx +43 -0
  136. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  137. package/src/components/chat/acp/blocks.tsx +3 -2
  138. package/src/components/chat/acp/session-tabs.tsx +8 -3
  139. package/src/components/chat/chat-components.tsx +2 -0
  140. package/src/components/chat/chat-panel.tsx +20 -101
  141. package/src/components/chat/chat-utils.ts +4 -8
  142. package/src/components/chat/notebook-access-selector.stories.tsx +22 -0
  143. package/src/components/chat/notebook-access-selector.tsx +100 -0
  144. package/src/components/data-table/TableBottomBar.tsx +4 -1
  145. package/src/components/data-table/TableTopBar.tsx +2 -0
  146. package/src/components/data-table/__tests__/chart-spec-model.test.ts +195 -195
  147. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  148. package/src/components/data-table/__tests__/columns.test.tsx +105 -0
  149. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  150. package/src/components/data-table/__tests__/data-table.test.tsx +1 -0
  151. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  152. package/src/components/data-table/__tests__/export-actions.test.tsx +105 -0
  153. package/src/components/data-table/__tests__/keyboard-selection.test.tsx +188 -0
  154. package/src/components/data-table/__tests__/pagination.test.tsx +28 -1
  155. package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
  156. package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
  157. package/src/components/data-table/cell-selection/feature.ts +2 -2
  158. package/src/components/data-table/cell-selection/types.ts +2 -2
  159. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
  160. package/src/components/data-table/charts/__tests__/spec-snapshot.test.ts +20 -20
  161. package/src/components/data-table/charts/__tests__/spec.test.ts +78 -52
  162. package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
  163. package/src/components/data-table/charts/chart-spec/spec.ts +68 -43
  164. package/src/components/data-table/charts/charts.tsx +40 -30
  165. package/src/components/data-table/charts/storage.ts +16 -5
  166. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  167. package/src/components/data-table/column-summary/chart-spec-model.tsx +23 -16
  168. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  169. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  170. package/src/components/data-table/columns.tsx +45 -46
  171. package/src/components/data-table/context-menu.tsx +36 -20
  172. package/src/components/data-table/data-table.tsx +17 -1
  173. package/src/components/data-table/date-popover.tsx +6 -4
  174. package/src/components/data-table/export-actions.tsx +76 -48
  175. package/src/components/data-table/pagination.tsx +44 -33
  176. package/src/components/data-table/range-focus/atoms.ts +4 -6
  177. package/src/components/data-table/range-focus/use-cell-range-selection.ts +65 -38
  178. package/src/components/data-table/range-focus/use-scroll-into-view.ts +21 -2
  179. package/src/components/data-table/renderers.tsx +16 -7
  180. package/src/components/data-table/row-viewer-panel/__tests__/row-viewer.test.tsx +24 -1
  181. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +4 -4
  182. package/src/components/data-table/search-highlight.tsx +79 -0
  183. package/src/components/data-table/types.ts +1 -0
  184. package/src/components/data-table/url-detector.tsx +49 -9
  185. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  186. package/src/components/datasources/__tests__/column-preview.test.tsx +96 -5
  187. package/src/components/datasources/__tests__/datasources-interactions.test.tsx +181 -0
  188. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  189. package/src/components/datasources/__tests__/utils.test.ts +57 -6
  190. package/src/components/datasources/column-preview.tsx +17 -12
  191. package/src/components/datasources/datasources.tsx +55 -42
  192. package/src/components/datasources/utils.ts +32 -23
  193. package/src/components/debugger/debugger-code.tsx +5 -0
  194. package/src/components/dependency-graph/dependency-graph-tree.tsx +8 -8
  195. package/src/components/dependency-graph/elements.ts +24 -21
  196. package/src/components/dependency-graph/minimap-content.tsx +2 -6
  197. package/src/components/dependency-graph/panels.tsx +4 -2
  198. package/src/components/dependency-graph/utils/__tests__/layout.test.ts +18 -11
  199. package/src/components/editor/SortableCell.tsx +1 -3
  200. package/src/components/editor/__tests__/Output.test.tsx +50 -32
  201. package/src/{core/attachments/__tests__/toast.test.tsx → components/editor/__tests__/agent-toast.test.tsx} +1 -1
  202. package/src/components/editor/__tests__/data-attributes.test.tsx +28 -13
  203. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  204. package/src/components/editor/__tests__/output-persistence.test.tsx +6 -1
  205. package/src/components/editor/__tests__/package-alert.test.tsx +22 -1
  206. package/src/components/editor/actions/__tests__/name-cell-input.test.tsx +69 -0
  207. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +34 -8
  208. package/src/components/editor/actions/__tests__/pair-with-agent.test.tsx +2 -6
  209. package/src/components/editor/actions/name-cell-input.tsx +4 -3
  210. package/src/components/editor/actions/pair-with-agent-commands.ts +2 -2
  211. package/src/components/editor/actions/pair-with-agent-modal.tsx +19 -16
  212. package/src/components/editor/actions/useCellActionButton.tsx +20 -26
  213. package/src/components/editor/actions/useNotebookActions.tsx +14 -19
  214. package/src/components/editor/actions/useRestartKernel.tsx +1 -1
  215. package/src/components/editor/actions/useSetCodeVisibility.ts +10 -11
  216. package/src/components/editor/actions/useSetOutputsExpanded.ts +13 -18
  217. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  218. package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
  219. package/src/components/editor/alerts/__tests__/connection-notice.test.tsx +1 -1
  220. package/src/components/editor/alerts/connection-notice.tsx +1 -1
  221. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  222. package/src/components/editor/app-container.tsx +1 -1
  223. package/src/components/editor/boundary/__tests__/ErrorBoundary.test.tsx +1 -1
  224. package/src/components/editor/cell/CreateCellButton.tsx +15 -21
  225. package/src/components/editor/cell/DeleteButton.tsx +3 -2
  226. package/src/components/editor/cell/RunButton.tsx +2 -2
  227. package/src/components/editor/cell/StagedAICell.tsx +10 -2
  228. package/src/components/editor/cell/StopButton.tsx +2 -2
  229. package/src/components/editor/cell/__tests__/StagedAICell.test.tsx +3 -1
  230. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  231. package/src/components/editor/cell/cell-actions.tsx +12 -1
  232. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  233. package/src/components/editor/cell/code/cell-editor.tsx +22 -36
  234. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  235. package/src/components/editor/cell/collapse.tsx +5 -3
  236. package/src/components/editor/cell/useAddCell.ts +4 -8
  237. package/src/components/editor/cell/useDeleteCell.tsx +10 -36
  238. package/src/components/editor/cell/useRunCells.ts +2 -5
  239. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  240. package/src/components/editor/cell/useSplitCell.tsx +2 -1
  241. package/src/components/editor/chrome/components/__tests__/feedback-button.test.tsx +1 -1
  242. package/src/components/editor/chrome/components/feedback-button.tsx +1 -1
  243. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +31 -2
  244. package/src/components/editor/chrome/panels/__tests__/runtime-details.test.tsx +1 -1
  245. package/src/components/editor/chrome/panels/__tests__/sandbox-recovery.test.tsx +1 -1
  246. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  247. package/src/components/editor/chrome/panels/logs-panel.tsx +2 -1
  248. package/src/components/editor/chrome/panels/outline/__tests__/outline-keyboard.test.tsx +84 -0
  249. package/src/components/editor/chrome/panels/outline/floating-outline.tsx +39 -14
  250. package/src/components/editor/chrome/panels/outline/useActiveOutline.tsx +3 -2
  251. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  252. package/src/components/editor/chrome/panels/secrets-panel.tsx +1 -0
  253. package/src/components/editor/chrome/panels/snippets-panel.tsx +10 -12
  254. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +9 -18
  255. package/src/components/editor/chrome/wrapper/__tests__/document-sync-status.test.tsx +92 -0
  256. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +61 -2
  257. package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +4 -4
  258. package/src/components/editor/chrome/wrapper/footer-items/backend-status.tsx +1 -1
  259. package/src/components/editor/chrome/wrapper/footer-items/document-sync-status.tsx +54 -0
  260. package/src/components/editor/chrome/wrapper/footer-items/lsp-status.tsx +1 -1
  261. package/src/components/editor/chrome/wrapper/footer-items/machine-stats.tsx +1 -1
  262. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  263. package/src/components/editor/chrome/wrapper/footer.tsx +2 -0
  264. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
  265. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  266. package/src/components/editor/columns/sortable-column.tsx +1 -1
  267. package/src/components/editor/connections/components.tsx +5 -10
  268. package/src/components/editor/controls/Controls.tsx +3 -3
  269. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  270. package/src/components/editor/controls/__tests__/keyboard-shortcuts.test.tsx +61 -0
  271. package/src/components/editor/controls/__tests__/notebook-menu-dropdown.test.tsx +10 -4
  272. package/src/components/editor/controls/command-palette.tsx +22 -10
  273. package/src/components/editor/controls/keyboard-shortcuts.tsx +14 -5
  274. package/src/components/editor/documentation.css +34 -11
  275. package/src/components/editor/dynamic-favicon.tsx +21 -40
  276. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +9 -23
  277. package/src/components/editor/errors/auto-fix.tsx +4 -8
  278. package/src/components/editor/errors/send-error-report-button.tsx +3 -6
  279. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  280. package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +47 -0
  281. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  282. package/src/components/editor/file-tree/file-explorer.tsx +48 -15
  283. package/src/components/editor/file-tree/file-name-input.tsx +1 -0
  284. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  285. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  286. package/src/components/editor/header/__tests__/status.test.tsx +5 -3
  287. package/src/components/editor/header/app-header.tsx +1 -1
  288. package/src/components/editor/header/status.tsx +40 -20
  289. package/src/components/editor/links/__tests__/cell-link.test.tsx +148 -0
  290. package/src/components/editor/links/cell-link.tsx +16 -6
  291. package/src/components/editor/navigation/__tests__/clipboard.test.ts +60 -38
  292. package/src/components/editor/navigation/__tests__/navigation.test.ts +73 -64
  293. package/src/components/editor/navigation/clipboard.ts +12 -11
  294. package/src/components/editor/navigation/focus-utils.ts +4 -4
  295. package/src/components/editor/navigation/multi-cell-action-toolbar.tsx +11 -20
  296. package/src/components/editor/navigation/navigation.ts +20 -28
  297. package/src/components/editor/notebook-banner.tsx +1 -0
  298. package/src/components/editor/notebook-cell.tsx +19 -13
  299. package/src/components/editor/output/MarimoErrorOutput.tsx +2 -1
  300. package/src/components/editor/output/MarimoTracebackOutput.tsx +19 -17
  301. package/src/components/editor/output/Outputs.css +8 -0
  302. package/src/components/editor/output/__tests__/traceback.test.tsx +27 -1
  303. package/src/components/editor/output/ansi-reduce.ts +33 -20
  304. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  305. package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +125 -69
  306. package/src/components/editor/package-alert.tsx +14 -7
  307. package/src/components/editor/renderers/cell-array.tsx +10 -17
  308. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +12 -6
  309. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  310. package/src/components/home/components.tsx +17 -31
  311. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  312. package/src/components/icons/large-spinner.tsx +3 -14
  313. package/src/components/icons/spinner.tsx +1 -1
  314. package/src/components/markdown/markdown-renderer.tsx +4 -8
  315. package/src/components/mcp/mcp-status-indicator.tsx +1 -1
  316. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  317. package/src/components/pages/edit-page.tsx +32 -17
  318. package/src/components/pages/home-page.tsx +164 -91
  319. package/src/components/scratchpad/scratchpad.test.tsx +97 -0
  320. package/src/components/scratchpad/scratchpad.tsx +20 -10
  321. package/src/components/slides/__tests__/minimap-actions.test.tsx +9 -25
  322. package/src/components/slides/__tests__/slide-scroll-hint.test.tsx +37 -0
  323. package/src/components/slides/minimap.tsx +7 -3
  324. package/src/components/slides/reveal-component.tsx +57 -25
  325. package/src/components/slides/reveal-slides.css +5 -4
  326. package/src/components/slides/slide-cell-view.tsx +4 -4
  327. package/src/components/slides/slide-form.tsx +1 -0
  328. package/src/components/slides/slide-scroll-hint.tsx +3 -1
  329. package/src/components/sort/SortableCellsProvider.tsx +2 -1
  330. package/src/components/tracing/tracing-keyboard.test.tsx +85 -0
  331. package/src/components/tracing/tracing.tsx +45 -41
  332. package/src/components/ui/__tests__/draggable-popover.test.tsx +24 -0
  333. package/src/components/ui/__tests__/range-slider.test.tsx +165 -0
  334. package/src/components/ui/alert-dialog.tsx +1 -1
  335. package/src/components/ui/draggable-popover.tsx +24 -2
  336. package/src/components/ui/range-slider.tsx +22 -9
  337. package/src/core/ai/__tests__/staged-cells.test.ts +26 -25
  338. package/src/core/ai/context/providers/datasource.ts +6 -2
  339. package/src/core/ai/staged-cells.ts +15 -18
  340. package/src/core/ai/tools/__tests__/edit-notebook-tool.test.ts +6 -22
  341. package/src/core/ai/tools/__tests__/registry.test.ts +17 -0
  342. package/src/core/ai/tools/base.ts +18 -15
  343. package/src/core/ai/tools/edit-notebook-tool.ts +75 -71
  344. package/src/core/ai/tools/registry.ts +13 -10
  345. package/src/core/ai/tools/sample-tool.ts +6 -7
  346. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -0
  347. package/src/core/cells/__tests__/actions.test.ts +474 -34
  348. package/src/core/cells/__tests__/add-missing-import.test.ts +7 -8
  349. package/src/core/cells/__tests__/cell.test.ts +39 -0
  350. package/src/core/cells/__tests__/cells.test.ts +260 -1165
  351. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
  352. package/src/core/cells/__tests__/document.test.ts +414 -0
  353. package/src/core/cells/__tests__/flush-document.test.ts +40 -0
  354. package/src/core/cells/__tests__/notebook-outline.test.ts +114 -0
  355. package/src/core/cells/__tests__/outputs.test.tsx +71 -53
  356. package/src/core/cells/__tests__/session.test.ts +117 -36
  357. package/src/core/cells/__tests__/with-document-save.test.ts +67 -0
  358. package/src/core/cells/actions.ts +391 -3
  359. package/src/core/cells/add-missing-import.ts +46 -42
  360. package/src/core/cells/cell.ts +14 -3
  361. package/src/core/cells/cells.ts +99 -599
  362. package/src/core/cells/columns.ts +38 -0
  363. package/src/core/cells/document.ts +392 -0
  364. package/src/core/cells/outputs.ts +12 -28
  365. package/src/core/cells/pending-delete-service.ts +2 -6
  366. package/src/core/cells/session.ts +1 -1
  367. package/src/core/cells/types.ts +22 -1
  368. package/src/core/codemirror/__tests__/setup.test.ts +29 -29
  369. package/src/core/codemirror/ai/__tests__/resources.test.ts +117 -0
  370. package/src/core/codemirror/ai/resources.ts +1 -1
  371. package/src/core/codemirror/cells/__tests__/extensions.test.ts +42 -1
  372. package/src/core/codemirror/cells/extensions.ts +27 -9
  373. package/src/core/codemirror/cells/state.ts +1 -1
  374. package/src/core/codemirror/format.ts +6 -5
  375. package/src/core/codemirror/keymaps/vim.ts +4 -8
  376. package/src/core/codemirror/language/__tests__/sql.test.ts +22 -20
  377. package/src/core/codemirror/language/__tests__/utils.test.ts +187 -15
  378. package/src/core/codemirror/language/cell-code.ts +114 -0
  379. package/src/core/codemirror/language/extension.ts +52 -35
  380. package/src/core/codemirror/language/languages/markdown.ts +8 -6
  381. package/src/core/codemirror/language/languages/python.ts +16 -10
  382. package/src/core/codemirror/language/languages/sql/sql.ts +16 -24
  383. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  384. package/src/core/codemirror/language/types.ts +7 -7
  385. package/src/core/codemirror/language/utils.ts +9 -39
  386. package/src/core/codemirror/lsp/notebook-lsp.ts +6 -5
  387. package/src/core/codemirror/readonly/__tests__/extension.test.ts +1 -1
  388. package/src/core/codemirror/readonly/extension.ts +2 -2
  389. package/src/core/codemirror/rtc/extension.ts +24 -13
  390. package/src/core/codemirror/rtc/loro/awareness.ts +34 -19
  391. package/src/core/codemirror/rtc/loro/sync.ts +1 -0
  392. package/src/core/codemirror/utils.ts +1 -0
  393. package/src/core/config/__tests__/config-schema.test.ts +12 -5
  394. package/src/core/config/config-schema.ts +7 -1
  395. package/src/core/datasets/__tests__/data-source-discovery.test.ts +5 -5
  396. package/src/core/datasets/data-source-discovery.ts +4 -6
  397. package/src/core/edit-app.tsx +19 -23
  398. package/src/core/errors/error-entries.ts +3 -0
  399. package/src/core/export/hooks.ts +1 -1
  400. package/src/core/export/layout.ts +2 -2
  401. package/src/core/hotkeys/actions.ts +2 -4
  402. package/src/core/islands/__tests__/bootstrap.test.ts +73 -43
  403. package/src/core/islands/__tests__/bridge.test.ts +72 -25
  404. package/src/core/islands/bootstrap.ts +46 -53
  405. package/src/core/islands/bridge.ts +74 -23
  406. package/src/core/islands/components/__tests__/IslandControls.test.tsx +7 -5
  407. package/src/core/islands/worker/__tests__/controller.test.ts +62 -4
  408. package/src/core/islands/worker/controller.ts +4 -10
  409. package/src/core/islands/worker/worker.tsx +33 -33
  410. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  411. package/src/core/kernel/__tests__/handlers.test.ts +7 -153
  412. package/src/core/kernel/handlers.ts +18 -59
  413. package/src/core/network/__tests__/connection.test.ts +1 -1
  414. package/src/core/network/__tests__/requests-lazy.test.ts +1 -3
  415. package/src/core/network/connection-notice.ts +1 -1
  416. package/src/core/network/connection.ts +1 -1
  417. package/src/core/network/requests-lazy.ts +1 -4
  418. package/src/core/network/requests-network.ts +0 -25
  419. package/src/core/network/requests-static.ts +0 -3
  420. package/src/core/network/requests-toasting.tsx +0 -3
  421. package/src/core/network/types.ts +0 -12
  422. package/src/core/network/useConnectionNotice.ts +8 -2
  423. package/src/core/notebook/attachments.ts +38 -0
  424. package/src/core/notebook/document/__tests__/codemirror.test.ts +552 -0
  425. package/src/core/notebook/document/__tests__/draft.test.ts +363 -0
  426. package/src/core/notebook/document/__tests__/fixtures.ts +92 -0
  427. package/src/core/notebook/document/__tests__/hold.test.ts +88 -0
  428. package/src/core/notebook/document/__tests__/hooks.test.tsx +391 -0
  429. package/src/core/notebook/document/__tests__/lifecycle-scenarios.test.ts +61 -0
  430. package/src/core/notebook/document/__tests__/model.test.ts +247 -0
  431. package/src/core/notebook/document/__tests__/protocol-fixtures.ts +38 -0
  432. package/src/core/notebook/document/__tests__/recovery-scenarios.test.ts +230 -0
  433. package/src/core/notebook/document/__tests__/scenario-fixture.ts +43 -0
  434. package/src/core/notebook/document/__tests__/scenarios.test.ts +205 -0
  435. package/src/core/notebook/document/__tests__/state.test.ts +459 -0
  436. package/src/core/notebook/document/__tests__/submission.test.ts +203 -0
  437. package/src/core/notebook/document/__tests__/test-host.test.ts +92 -0
  438. package/src/core/notebook/document/__tests__/test-host.ts +247 -0
  439. package/src/core/notebook/document/__tests__/test-notebook.ts +149 -0
  440. package/src/core/notebook/document/__tests__/user.ts +61 -0
  441. package/src/core/notebook/document/codemirror.ts +216 -0
  442. package/src/core/notebook/document/draft.ts +502 -0
  443. package/src/core/notebook/document/hooks.tsx +118 -0
  444. package/src/core/notebook/document/index.ts +24 -0
  445. package/src/core/notebook/document/model.ts +105 -0
  446. package/src/core/notebook/document/select.ts +41 -0
  447. package/src/core/notebook/document/state.ts +378 -0
  448. package/src/core/notebook/document/sync.ts +418 -0
  449. package/src/core/notebook/index.ts +18 -0
  450. package/src/core/notebook/replica/AGENTS.md +11 -0
  451. package/src/core/notebook/replica/__tests__/attachments.test.ts +56 -0
  452. package/src/core/notebook/replica/__tests__/document.test.ts +126 -0
  453. package/src/core/notebook/replica/__tests__/fixtures.ts +79 -0
  454. package/src/core/notebook/replica/__tests__/replica.test.ts +550 -0
  455. package/src/core/notebook/replica/backend.ts +19 -0
  456. package/src/core/notebook/replica/connection.ts +42 -0
  457. package/src/core/notebook/replica/index.ts +13 -0
  458. package/src/core/notebook/replica/replica.ts +286 -0
  459. package/src/core/notebook/replica/resources/attachments.ts +22 -0
  460. package/src/core/notebook/replica/resources/document.ts +101 -0
  461. package/src/core/packages/__tests__/useSandboxController.test.tsx +1 -1
  462. package/src/core/packages/useSandboxController.ts +1 -1
  463. package/src/core/run-app.tsx +48 -51
  464. package/src/core/runtime/__tests__/adapter.test.ts +1 -1
  465. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  466. package/src/core/runtime/__tests__/runtime.test.ts +32 -58
  467. package/src/core/runtime/adapter.ts +1 -1
  468. package/src/core/runtime/config.ts +2 -2
  469. package/src/core/runtime/runtime.ts +38 -42
  470. package/src/core/runtime/types.ts +0 -2
  471. package/src/core/saving/filename.ts +1 -1
  472. package/src/core/saving/save-component.tsx +10 -4
  473. package/src/core/saving/useAutoSave.ts +1 -1
  474. package/src/core/session/__tests__/backend.test.ts +235 -0
  475. package/src/core/session/__tests__/useNotebookReplica.hook.test.tsx +183 -0
  476. package/src/core/session/__tests__/useNotebookSession.hook.test.tsx +663 -0
  477. package/src/core/session/active-notebook.ts +11 -0
  478. package/src/core/session/backend.ts +125 -0
  479. package/src/core/{websocket → session}/types.ts +2 -2
  480. package/src/core/session/useNotebookReplica.ts +72 -0
  481. package/src/core/{websocket → session}/useNotebookSession.tsx +74 -68
  482. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  483. package/src/core/state/jotai.ts +1 -1
  484. package/src/core/static/__tests__/files.test.ts +38 -10
  485. package/src/core/static/files.ts +2 -0
  486. package/src/core/{websocket → transports}/__tests__/connection-state.test.ts +1 -13
  487. package/src/core/transports/__tests__/server.test.ts +408 -0
  488. package/src/core/transports/__tests__/static.test.ts +175 -0
  489. package/src/core/transports/__tests__/wasm.test.ts +166 -0
  490. package/src/core/{websocket → transports}/connection-state.ts +9 -16
  491. package/src/core/transports/server.ts +152 -0
  492. package/src/core/transports/static.ts +84 -0
  493. package/src/core/transports/wasm.ts +97 -0
  494. package/src/core/wasm/__tests__/bridge.test.ts +213 -50
  495. package/src/core/wasm/__tests__/worker.test.ts +187 -0
  496. package/src/core/wasm/bridge.ts +75 -69
  497. package/src/core/wasm/rpc.ts +15 -3
  498. package/src/core/wasm/store.ts +3 -3
  499. package/src/core/wasm/worker/bootstrap.ts +55 -51
  500. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  501. package/src/core/wasm/worker/save-worker.ts +9 -10
  502. package/src/core/wasm/worker/types.ts +29 -18
  503. package/src/core/wasm/worker/worker.ts +88 -44
  504. package/src/css/globals.css +66 -22
  505. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  506. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  507. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  508. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  509. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  510. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  511. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  512. package/src/hooks/useAsyncData.ts +3 -3
  513. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  514. package/src/hooks/useDebounce.ts +6 -10
  515. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  516. package/src/hooks/useDelayElapsed.ts +14 -10
  517. package/src/hooks/useElapsedTime.ts +3 -3
  518. package/src/hooks/useEventListener.ts +1 -2
  519. package/src/hooks/useInputHistory.ts +4 -7
  520. package/src/hooks/useInternalStateWithSync.ts +6 -6
  521. package/src/hooks/useResizeObserver.ts +4 -7
  522. package/src/hooks/useScript.ts +67 -70
  523. package/src/hooks/useStateForKey.ts +93 -0
  524. package/src/mount.tsx +1 -5
  525. package/src/plugins/__tests__/plugin-object-schemas.test.ts +104 -0
  526. package/src/plugins/core/registerReactComponent.tsx +1 -1
  527. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  528. package/src/plugins/impl/DataEditorPlugin.tsx +1 -1
  529. package/src/plugins/impl/DataTablePlugin.tsx +69 -37
  530. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  531. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  532. package/src/plugins/impl/FormPlugin.tsx +1 -1
  533. package/src/plugins/impl/RangeSliderPlugin.tsx +16 -2
  534. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  535. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  536. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +387 -1
  537. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  538. package/src/plugins/impl/__tests__/RangeSliderPlugin.test.tsx +109 -0
  539. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  540. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +153 -0
  541. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  542. package/src/plugins/impl/anywidget/model.ts +3 -1
  543. package/src/plugins/impl/anywidget/registry.ts +18 -14
  544. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  545. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  546. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  547. package/src/plugins/impl/chat/chat-ui.tsx +14 -11
  548. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  549. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  550. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  551. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  552. package/src/plugins/impl/data-explorer/components/column-summary.tsx +4 -2
  553. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  554. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -2
  555. package/src/plugins/impl/data-frames/__tests__/sidebar.test.tsx +160 -0
  556. package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +66 -66
  557. package/src/plugins/impl/data-frames/panel.tsx +43 -16
  558. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  559. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  560. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  561. package/src/plugins/impl/panel/utils.ts +17 -0
  562. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +2 -5
  563. package/src/plugins/impl/plotly/selection.ts +25 -19
  564. package/src/plugins/impl/vega/VegaPlugin.tsx +2 -3
  565. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  566. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  567. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  568. package/src/stories/button.stories.tsx +1 -0
  569. package/src/stories/cell.stories.tsx +1 -1
  570. package/src/stories/data-table.stories.tsx +31 -0
  571. package/src/stories/editor.stories.tsx +24 -17
  572. package/src/stories/form-wrapper.stories.tsx +2 -2
  573. package/src/stories/layout/vertical/one-column.stories.tsx +1 -1
  574. package/src/stories/runtime-story.tsx +23 -16
  575. package/src/stories/select.stories.tsx +2 -2
  576. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  577. package/src/utils/__tests__/arrays.test.ts +35 -0
  578. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  579. package/src/utils/__tests__/{createReducer.test.ts → createReducer.test.tsx} +29 -0
  580. package/src/utils/__tests__/numbers.test.ts +105 -1
  581. package/src/utils/__tests__/objects.test.ts +21 -0
  582. package/src/utils/arrays.ts +11 -2
  583. package/src/utils/blob.ts +1 -1
  584. package/src/utils/createReducer.ts +53 -26
  585. package/src/utils/maps.ts +0 -3
  586. package/src/utils/numbers.ts +79 -18
  587. package/src/utils/objects.ts +4 -9
  588. package/src/utils/once.ts +19 -19
  589. package/src/utils/tracer.ts +9 -5
  590. package/src/utils/zod-utils.ts +1 -3
  591. package/dist/any-language-editor-DZ_YTVus.js +0 -76
  592. package/dist/assets/__vite-browser-external-BXYkiFsk.js +0 -1
  593. package/dist/assets/worker-HPY9fbhz.js +0 -114
  594. package/dist/mermaid-HWGCJPDP-ByCsZnYF.js +0 -2
  595. package/dist/precisionRound-DMIVQrt2.js +0 -97
  596. package/src/core/ai/tools/__tests__/run-cells-tool.test.ts +0 -656
  597. package/src/core/ai/tools/run-cells-tool.ts +0 -303
  598. package/src/core/attachments/state.ts +0 -21
  599. package/src/core/cells/__tests__/apply-transaction.test.ts +0 -991
  600. package/src/core/cells/__tests__/document-changes.test.ts +0 -1140
  601. package/src/core/cells/__tests__/document-roundtrip.test.ts +0 -392
  602. package/src/core/cells/document-changes.ts +0 -944
  603. package/src/core/host/AGENTS.md +0 -6
  604. package/src/core/host/__tests__/events.test.ts +0 -46
  605. package/src/core/host/__tests__/notebook-client.test.ts +0 -191
  606. package/src/core/host/__tests__/protocol.test.ts +0 -214
  607. package/src/core/host/connection.ts +0 -81
  608. package/src/core/host/events.ts +0 -60
  609. package/src/core/host/notebook-client.ts +0 -226
  610. package/src/core/host/protocol.ts +0 -1943
  611. package/src/core/state/__tests__/jotai.test.ts +0 -25
  612. package/src/core/wasm/worker/message-buffer.ts +0 -33
  613. package/src/core/websocket/__tests__/createConnectionTransport.test.ts +0 -49
  614. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +0 -140
  615. package/src/core/websocket/__tests__/useNotebookSession.hook.test.tsx +0 -883
  616. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  617. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -335
  618. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  619. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  620. package/src/core/websocket/transports/basic.ts +0 -102
  621. package/src/core/websocket/transports/create.ts +0 -31
  622. package/src/core/websocket/transports/sse.ts +0 -308
  623. package/src/core/websocket/transports/transport.ts +0 -50
  624. package/src/core/websocket/transports/ws.ts +0 -98
  625. package/src/core/websocket/useNotebookClient.ts +0 -80
  626. /package/src/{core/attachments/toast.ts → components/editor/agent-toast.ts} +0 -0
  627. /package/src/core/{websocket → session}/connection-utils.ts +0 -0
@@ -0,0 +1,125 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ /** The backend the editor follows, chosen once for its mode. */
4
+
5
+ import { NotebookClient, StreamStopped } from "@marimo-team/client";
6
+ import type { NotebookId } from "@marimo-team/client/protocol";
7
+ import type { NotebookBackend } from "../notebook/replica";
8
+ import { PyodideBridge } from "../wasm/bridge";
9
+ import { isWasm } from "../wasm/utils";
10
+ import { sessionSse, websocket, withHeaders } from "../transports/server";
11
+ import { idle } from "../transports/static";
12
+
13
+ /** How the editor follows its notebook. */
14
+ export interface EditorBackendOptions {
15
+ static: boolean;
16
+ transportType: "websocket" | "sse";
17
+ /** The session route, read before each connection. */
18
+ url: () => string;
19
+ /** The runtime's headers, read before each request. */
20
+ headers: () => Record<string, string>;
21
+ /** The host URL of a notebook the stream reported. */
22
+ notebookUrl: (notebookId: NotebookId) => URL;
23
+ /** Wait until the host can accept the first connection. */
24
+ waitUntilReady: () => Promise<void>;
25
+ /** Whether the host is reachable before an explicit reconnect. */
26
+ checkHealth: () => Promise<boolean>;
27
+ }
28
+
29
+ /**
30
+ * Read the session route once the host has started.
31
+ *
32
+ * Every connection waits for the same startup. Once it settles, even by
33
+ * giving up, connections read the route directly. An explicit reconnect
34
+ * checks the host's health instead.
35
+ */
36
+ function afterStartup({
37
+ url,
38
+ waitUntilReady,
39
+ }: Pick<EditorBackendOptions, "url" | "waitUntilReady">) {
40
+ let startup: Promise<void> | undefined;
41
+ let settled = false;
42
+ return async () => {
43
+ if (!settled) {
44
+ startup ??= waitUntilReady().finally(() => {
45
+ settled = true;
46
+ });
47
+ try {
48
+ await startup;
49
+ } catch (error) {
50
+ throw new StreamStopped("Healthy connection never made", {
51
+ cause: error,
52
+ });
53
+ }
54
+ }
55
+ return url();
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Follow the notebook running in the WASM worker.
61
+ *
62
+ * The worker's backend exists once the worker has started, so the first
63
+ * connection creates it. Commands are only for a notebook its stream reported.
64
+ */
65
+ function wasm(): NotebookBackend {
66
+ let backend: Promise<NotebookBackend> | undefined;
67
+ let started: NotebookBackend | undefined;
68
+ return {
69
+ transport: async (attempt, handlers) => {
70
+ backend ??= PyodideBridge.INSTANCE.createNotebookBackend();
71
+ try {
72
+ started = await backend;
73
+ } catch (error) {
74
+ throw new StreamStopped("The WASM notebook did not start", {
75
+ cause: error,
76
+ });
77
+ }
78
+ return started.transport(attempt, handlers);
79
+ },
80
+ notebook: (id) => {
81
+ if (started === undefined) {
82
+ throw new Error("The WASM notebook has not started");
83
+ }
84
+ return started.notebook(id);
85
+ },
86
+ };
87
+ }
88
+
89
+ /** Commands for a notebook the editor cannot change through its backend. */
90
+ function uneditable(): ReturnType<NotebookBackend["notebook"]> {
91
+ const unsupported: NotebookClient["save"] = async () => ({
92
+ ok: false,
93
+ status: 501,
94
+ problem: {
95
+ type: "tag:marimo.io,2026:host/unsupported-operation",
96
+ title: "Document mutations are unavailable on this connection",
97
+ status: 501,
98
+ },
99
+ });
100
+ return { edit: () => unsupported(), save: unsupported };
101
+ }
102
+
103
+ /** Choose the backend for the editor's notebook. */
104
+ export function createEditorBackend(
105
+ options: EditorBackendOptions,
106
+ ): NotebookBackend {
107
+ if (options.static) {
108
+ return { transport: idle, notebook: uneditable };
109
+ }
110
+ if (isWasm()) {
111
+ return wasm();
112
+ }
113
+ const url = afterStartup(options);
114
+ return {
115
+ transport:
116
+ options.transportType === "sse"
117
+ ? sessionSse(url, options.headers)
118
+ : websocket(url),
119
+ notebook: (id) =>
120
+ new NotebookClient(options.notebookUrl(id), {
121
+ fetch: withHeaders(options.headers),
122
+ }),
123
+ checkHealth: options.checkHealth,
124
+ };
125
+ }
@@ -1,12 +1,12 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import type { ConnectionStatus as HostConnectionStatus } from "../host/connection";
3
+ import type { ConnectionStatus as HostConnectionStatus } from "../notebook/replica";
4
4
  import type { NotificationMessageData } from "../kernel/messages";
5
5
 
6
6
  export {
7
7
  ConnectionState,
8
8
  ConnectionClosedReason as WebSocketClosedReason,
9
- } from "../host/connection";
9
+ } from "../notebook/replica";
10
10
 
11
11
  export type ConnectionPhase =
12
12
  | NotificationMessageData<"startup-progress">["phase"]
@@ -0,0 +1,72 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ /** The editor session's replica, following its backend while mounted. */
4
+
5
+ import { useStore } from "jotai";
6
+ import { useEffect, useMemo } from "react";
7
+ import useEvent from "react-use-event-hook";
8
+ import type {
9
+ NotificationMessage,
10
+ NotificationPayload,
11
+ } from "../kernel/messages";
12
+ import {
13
+ createNotebookReplica,
14
+ type NotebookReplica,
15
+ } from "../notebook/replica";
16
+ import { activeNotebookReplicaAtom } from "./active-notebook";
17
+ import { createEditorBackend, type EditorBackendOptions } from "./backend";
18
+
19
+ type Options = EditorBackendOptions & {
20
+ onNotification: (notification: NotificationPayload) => void;
21
+ };
22
+
23
+ export function useNotebookReplica(options: Options): NotebookReplica {
24
+ const store = useStore();
25
+ const { static: isStatic, transportType } = options;
26
+ const replica = useMemo(() => createNotebookReplica(store), [store]);
27
+ const url = useEvent(options.url);
28
+ const headers = useEvent(options.headers);
29
+ const notebookUrl = useEvent(options.notebookUrl);
30
+ const waitUntilReady = useEvent(options.waitUntilReady);
31
+ const checkHealth = useEvent(options.checkHealth);
32
+ const onNotification = useEvent(options.onNotification);
33
+ // Opens nothing until the replica follows it.
34
+ const backend = useMemo(
35
+ () =>
36
+ createEditorBackend({
37
+ static: isStatic,
38
+ transportType,
39
+ url,
40
+ headers,
41
+ notebookUrl,
42
+ waitUntilReady,
43
+ checkHealth,
44
+ }),
45
+ [
46
+ isStatic,
47
+ transportType,
48
+ url,
49
+ headers,
50
+ notebookUrl,
51
+ waitUntilReady,
52
+ checkHealth,
53
+ ],
54
+ );
55
+
56
+ useEffect(() => {
57
+ store.set(activeNotebookReplicaAtom, replica);
58
+ const disconnect = replica.connect(backend, {
59
+ // The host validates the envelope, not the native notification contents.
60
+ onNotification: (notification) =>
61
+ onNotification({ data: notification as NotificationMessage }),
62
+ });
63
+ return () => {
64
+ disconnect();
65
+ if (store.get(activeNotebookReplicaAtom) === replica) {
66
+ store.set(activeNotebookReplicaAtom, null);
67
+ }
68
+ };
69
+ }, [replica, backend, store, onNotification]);
70
+
71
+ return replica;
72
+ }
@@ -2,18 +2,19 @@
2
2
 
3
3
  import { invalidatePackageData } from "@/core/packages/package-data";
4
4
  import { useAtom, useAtomValue, useSetAtom, useStore } from "jotai";
5
- import { useLayoutEffect } from "react";
5
+ import { useEffect, useLayoutEffect, useMemo } from "react";
6
6
  import { useErrorBoundary } from "react-error-boundary";
7
7
  import { toast } from "@/components/ui/use-toast";
8
- import { attachmentsAtom } from "@/core/attachments/state";
9
- import { getNotebook, useCellActions } from "@/core/cells/cells";
10
- import { applyTransactionChanges } from "@/core/cells/document-changes";
8
+ import {
9
+ getNotebook,
10
+ notebookAtom,
11
+ useReducerActions,
12
+ } from "@/core/cells/cells";
13
+ import { followDocument } from "@/core/cells/document";
11
14
  import { AUTOCOMPLETER } from "@/core/codemirror/completion/Autocompleter";
12
- import type {
13
- NotificationMessageData,
14
- NotificationPayload,
15
- } from "@/core/kernel/messages";
16
- import { useNotebookClient } from "@/core/websocket/useNotebookClient";
15
+ import { createDocumentModel } from "@/core/notebook/document";
16
+ import type { NotificationPayload } from "@/core/kernel/messages";
17
+ import { useNotebookReplica } from "@/core/session/useNotebookReplica";
17
18
  import { renderHTML } from "@/plugins/core/RenderHTML";
18
19
  import {
19
20
  handleWidgetMessage,
@@ -26,14 +27,13 @@ import { Logger } from "@/utils/Logger";
26
27
  import { reloadSafe } from "@/utils/reload-safe";
27
28
  import { useAlertActions } from "../alerts/state";
28
29
  import { cacheInfoAtom } from "../cache/requests";
29
- import { SCRATCH_CELL_ID } from "../cells/ids";
30
30
  import { useRunsActions } from "../cells/runs";
31
31
  import { focusAndScrollCellOutputIntoView } from "../cells/scrollCellIntoView";
32
32
  import {
33
33
  resyncBreakpoints,
34
34
  setActiveLine,
35
35
  } from "../codemirror/cells/debugger-state";
36
- import type { CellData } from "../cells/types";
36
+ import { notebookCells } from "../cells/utils";
37
37
  import { capabilitiesAtom } from "../config/capabilities";
38
38
  import { connectionTransportTypeAtom, useSetAppConfig } from "../config/config";
39
39
  import { useDataSourceActions } from "../datasets/data-source-connections";
@@ -66,7 +66,7 @@ import { type LayoutState, useLayoutActions } from "../layout/state";
66
66
  import { kioskModeAtom } from "../mode";
67
67
  import { connectionAtom, startupProgressAtom } from "../network/connection";
68
68
  import type { RequestId } from "../network/DeferredRequestRegistry";
69
- import { runtimeConfigAtom, useRuntimeManager } from "../runtime/config";
69
+ import { useRuntimeManager } from "../runtime/config";
70
70
  import { SECRETS_REGISTRY } from "../secrets/request-registry";
71
71
  import { isStaticNotebook } from "../static/static-state";
72
72
  import {
@@ -76,43 +76,26 @@ import {
76
76
  import { useStorageActions } from "../storage/state";
77
77
  import { useVariablesActions } from "../variables/state";
78
78
  import type { VariableName } from "../variables/types";
79
- import { isWasm } from "../wasm/utils";
79
+ import { activeDocumentModelAtom } from "./active-notebook";
80
80
  import { WebSocketClosedReason, ConnectionState } from "./types";
81
81
 
82
- const SUPPORTS_LAZY_KERNELS = true;
83
-
84
- function getExistingCells(): CellData[] | undefined {
85
- if (!SUPPORTS_LAZY_KERNELS) {
86
- return undefined;
87
- }
88
-
89
- // Remove scratch pad
90
- return Object.values(getNotebook().cellData).filter(
91
- (cell) => cell.id !== SCRATCH_CELL_ID,
92
- );
93
- }
94
-
95
82
  /** Coordinate session startup and apply notifications to the editor. */
96
83
  export function useNotebookSession(opts: {
97
84
  sessionId: SessionId;
98
85
  autoInstantiate: boolean;
99
- setCells: (cells: CellData[], layout: LayoutState) => void;
86
+ /** Called with the layout the kernel reported, after its cells are ready. */
87
+ onKernelReady?: (layout: LayoutState) => void;
100
88
  }) {
101
- const { autoInstantiate, sessionId, setCells } = opts;
89
+ const { autoInstantiate, sessionId, onKernelReady } = opts;
102
90
  const { showBoundary } = useErrorBoundary();
91
+ const store = useStore();
103
92
 
104
- const { handleCellMessage } = useCellActions();
105
- const actionsWithoutMiddleware = useCellActions({ skipMiddleware: true });
106
-
107
- const handleDocumentTransaction = (
108
- transaction: NotificationMessageData<"notebook-document-transaction">["transaction"],
109
- ) => {
110
- applyTransactionChanges(
111
- transaction.changes,
112
- actionsWithoutMiddleware,
113
- () => getNotebook().cellIds.inOrderIds,
114
- );
115
- };
93
+ const {
94
+ handleCellMessage,
95
+ reconcileDocument,
96
+ setLastRun,
97
+ resetCellRuntimes,
98
+ } = useReducerActions();
116
99
  const { addCellNotification } = useRunsActions();
117
100
  const setKernelState = useSetAtom(kernelStateAtom);
118
101
  const setInitialRunCompleted = useSetAtom(initialRunCompletedAtom);
@@ -136,11 +119,9 @@ export function useNotebookSession(opts: {
136
119
  const setStableSessionId = useSetAtom(stableSessionIdAtom);
137
120
  const setCapabilities = useSetAtom(capabilitiesAtom);
138
121
  const runtimeManager = useRuntimeManager();
139
- const { notebookId } = useAtomValue(runtimeConfigAtom);
140
122
  const transportType = useAtomValue(connectionTransportTypeAtom);
141
123
  const setCacheInfo = useSetAtom(cacheInfoAtom);
142
124
  const setKernelStartupError = useSetAtom(kernelStartupErrorAtom);
143
- const setAttachments = useSetAtom(attachmentsAtom);
144
125
  const {
145
126
  setNamespaces: setStorageNamespaces,
146
127
  filterFromVariables: filterStorageFromVariables,
@@ -148,9 +129,6 @@ export function useNotebookSession(opts: {
148
129
 
149
130
  const handleNotification = (msg: NotificationPayload) => {
150
131
  switch (msg.data.op) {
151
- case "attachments":
152
- setAttachments(msg.data.attachments);
153
- return;
154
132
  case "reload":
155
133
  reloadSafe();
156
134
  return;
@@ -172,20 +150,19 @@ export function useNotebookSession(opts: {
172
150
  setInitialRunCompleted(
173
151
  Boolean(msg.data.resumed || msg.data.auto_instantiated),
174
152
  );
175
- const existingCells = getExistingCells();
176
-
177
- handleKernelReady(msg.data, {
153
+ const layout = handleKernelReady(msg.data, {
178
154
  autoInstantiate,
179
- setCells,
155
+ cells: notebookCells(store.get(notebookAtom)),
156
+ setLastRun,
157
+ resetCellRuntimes,
180
158
  setLayoutData,
181
159
  setAppConfig,
182
160
  setCapabilities,
183
161
  setKernelState,
184
162
  onError: showBoundary,
185
- existingCells,
186
163
  });
164
+ onKernelReady?.(layout);
187
165
  if (!msg.data.resumed) {
188
- setAttachments([]);
189
166
  // A freshly started kernel may expose a different environment
190
167
  // (e.g. new env vars); re-run discovery instead of reusing stale
191
168
  // suggestions from the previous kernel instance.
@@ -271,13 +248,18 @@ export function useNotebookSession(opts: {
271
248
  filterStorageFromVariables(variableNames);
272
249
  return;
273
250
  case "variable-values":
274
- setMetadata(
275
- msg.data.variables.map((v) => ({
251
+ setMetadata([
252
+ ...(msg.data.removed ?? []).map((v) => ({
253
+ name: v.name as VariableName,
254
+ dataType: undefined,
255
+ value: undefined,
256
+ })),
257
+ ...msg.data.variables.map((v) => ({
276
258
  name: v.name as VariableName,
277
259
  dataType: v.datatype,
278
260
  value: v.value,
279
261
  })),
280
- );
262
+ ]);
281
263
  return;
282
264
  case "alert":
283
265
  toast({
@@ -398,7 +380,7 @@ export function useNotebookSession(opts: {
398
380
  );
399
381
  return;
400
382
  case "notebook-document-transaction":
401
- handleDocumentTransaction(msg.data.transaction);
383
+ // Edits reach the notebook through the host document.
402
384
  return;
403
385
  case "consumer-capabilities":
404
386
  setKioskMode(!msg.data.consumer_capabilities.edit);
@@ -408,7 +390,6 @@ export function useNotebookSession(opts: {
408
390
  }
409
391
  };
410
392
 
411
- const store = useStore();
412
393
  const onMessageError = (error: unknown) => {
413
394
  toast({
414
395
  title: "Failed to handle message",
@@ -416,25 +397,23 @@ export function useNotebookSession(opts: {
416
397
  variant: "danger",
417
398
  });
418
399
  };
419
- const notebook = useNotebookClient({
420
- sessionId,
400
+ const replica = useNotebookReplica({
421
401
  static: isStaticNotebook(),
422
402
  transportType,
423
- messageFormat:
424
- notebookId && transportType === "websocket" ? "host" : "session",
425
403
  url: () =>
426
404
  transportType === "sse"
427
405
  ? runtimeManager.getSseURL(sessionId).toString()
428
406
  : runtimeManager.getWsURL(sessionId).toString(),
429
407
  // SSE uses headers for auth. WebSockets carry the token in the URL.
430
408
  headers: () => runtimeManager.headers(),
409
+ notebookUrl: (notebookId) => runtimeManager.getNotebookURL(notebookId),
410
+ // Only the host's transports read the URL, so static and WASM never wait.
431
411
  waitUntilReady: async () => {
432
- if (!isStaticNotebook() && !isWasm() && !runtimeManager.isSameOrigin) {
412
+ if (!runtimeManager.isSameOrigin) {
433
413
  await runtimeManager.waitForHealthy();
434
414
  }
435
415
  },
436
416
  checkHealth: () => runtimeManager.reconcileFromHealth(),
437
- onMessageError,
438
417
  onNotification: (notification) => {
439
418
  try {
440
419
  handleNotification(notification);
@@ -445,14 +424,42 @@ export function useNotebookSession(opts: {
445
424
  },
446
425
  });
447
426
 
427
+ const documentModel = useMemo(
428
+ () =>
429
+ createDocumentModel({
430
+ store,
431
+ documentAtom: replica.documentAtom,
432
+ backendAtom: replica.backendAtom,
433
+ }),
434
+ [store, replica],
435
+ );
436
+ // Edits outlive reconnects and end with the session. The notebook's cells
437
+ // follow the document for as long as the session is mounted.
438
+ useEffect(() => {
439
+ const controller = new AbortController();
440
+ documentModel.start({ signal: controller.signal });
441
+ store.set(activeDocumentModelAtom, documentModel);
442
+ followDocument({
443
+ store,
444
+ model: documentModel,
445
+ reconcile: reconcileDocument,
446
+ signal: controller.signal,
447
+ });
448
+ return () => {
449
+ controller.abort();
450
+ if (store.get(activeDocumentModelAtom) === documentModel) {
451
+ store.set(activeDocumentModelAtom, null);
452
+ }
453
+ };
454
+ }, [documentModel, store, reconcileDocument]);
455
+
448
456
  // Subscribe before passive connection setup: static/WASM transports can emit synchronously.
449
457
  useLayoutEffect(() => {
450
458
  const updateConnection = () => {
451
- const status = store.get(notebook.connectionAtom);
459
+ const status = store.get(replica.connectionAtom);
452
460
  if (status.state === ConnectionState.NOT_STARTED) {
453
461
  return;
454
462
  }
455
- setAttachments([]);
456
463
  if (status.state === ConnectionState.OPEN) {
457
464
  updateStartupProgress(null);
458
465
  return; // kernel-ready, not the socket, establishes session readiness.
@@ -480,17 +487,16 @@ export function useNotebookSession(opts: {
480
487
  return status;
481
488
  });
482
489
  };
483
- const unsubscribe = store.sub(notebook.connectionAtom, updateConnection);
490
+ const unsubscribe = store.sub(replica.connectionAtom, updateConnection);
484
491
  updateConnection();
485
492
  return unsubscribe;
486
493
  }, [
487
- notebook,
494
+ replica,
488
495
  store,
489
496
  setConnection,
490
- setAttachments,
491
497
  setKernelStartupError,
492
498
  updateStartupProgress,
493
499
  ]);
494
500
 
495
- return { notebook, connection, reconnect: notebook.reconnect };
501
+ return { replica, documentModel, connection, reconnect: replica.reconnect };
496
502
  }
@@ -0,0 +1,108 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { act, renderHook } from "@testing-library/react";
4
+ import { atom, Provider } from "jotai";
5
+ import type React from "react";
6
+ import { describe, expect, it, vi } from "vitest";
7
+ import { store, useJotaiEffect, waitFor } from "../jotai";
8
+
9
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
10
+ <Provider store={store}>{children}</Provider>
11
+ );
12
+
13
+ describe("waitFor function", () => {
14
+ it("should resolve when the atom satisfies the predicate", async () => {
15
+ const testAtom = atom(0);
16
+ store.set(testAtom, 10);
17
+
18
+ const result = await waitFor(testAtom, (value) => value === 10);
19
+ expect(result).toBe(10);
20
+ });
21
+
22
+ it("should resolve when the atom changes to satisfy the predicate", async () => {
23
+ const testAtom = atom(0);
24
+
25
+ setTimeout(() => store.set(testAtom, 15), 5);
26
+ setTimeout(() => store.set(testAtom, 20), 10);
27
+
28
+ const result = await waitFor(testAtom, (value) => value === 20);
29
+ expect(result).toBe(20);
30
+ });
31
+ });
32
+
33
+ describe("useJotaiEffect", () => {
34
+ it("calls the effect with the new and previous values", () => {
35
+ const testAtom = atom(0);
36
+ const effect = vi.fn();
37
+ renderHook(() => useJotaiEffect(testAtom, effect), { wrapper });
38
+
39
+ act(() => store.set(testAtom, 1));
40
+ act(() => store.set(testAtom, 2));
41
+
42
+ expect(effect.mock.calls).toEqual([
43
+ [1, 0],
44
+ [2, 1],
45
+ ]);
46
+ });
47
+
48
+ it("calls only the latest callback after a rerender", () => {
49
+ const testAtom = atom(0);
50
+ const first = vi.fn();
51
+ const second = vi.fn();
52
+ const { rerender } = renderHook(
53
+ ({ effect }) => useJotaiEffect(testAtom, effect),
54
+ { initialProps: { effect: first }, wrapper },
55
+ );
56
+
57
+ rerender({ effect: second });
58
+ act(() => store.set(testAtom, 1));
59
+
60
+ expect(first).not.toHaveBeenCalled();
61
+ expect(second).toHaveBeenCalledTimes(1);
62
+ });
63
+
64
+ it("subscribes once per rerender with an inline callback", () => {
65
+ const testAtom = atom(0);
66
+ const calls: number[] = [];
67
+ const { rerender } = renderHook(
68
+ () => useJotaiEffect(testAtom, (value) => calls.push(value)),
69
+ { wrapper },
70
+ );
71
+
72
+ rerender();
73
+ rerender();
74
+ act(() => store.set(testAtom, 1));
75
+
76
+ expect(calls).toEqual([1]);
77
+ });
78
+
79
+ it("stops listening to the previous atom when the atom changes", () => {
80
+ const firstAtom = atom(0);
81
+ const secondAtom = atom(0);
82
+ const effect = vi.fn();
83
+ const { rerender } = renderHook(
84
+ ({ target }) => useJotaiEffect(target, effect),
85
+ { initialProps: { target: firstAtom }, wrapper },
86
+ );
87
+
88
+ rerender({ target: secondAtom });
89
+ act(() => store.set(firstAtom, 1));
90
+ expect(effect).not.toHaveBeenCalled();
91
+
92
+ act(() => store.set(secondAtom, 1));
93
+ expect(effect).toHaveBeenCalledTimes(1);
94
+ });
95
+
96
+ it("unsubscribes on unmount", () => {
97
+ const testAtom = atom(0);
98
+ const effect = vi.fn();
99
+ const { unmount } = renderHook(() => useJotaiEffect(testAtom, effect), {
100
+ wrapper,
101
+ });
102
+
103
+ unmount();
104
+ act(() => store.set(testAtom, 1));
105
+
106
+ expect(effect).not.toHaveBeenCalled();
107
+ });
108
+ });
@@ -42,7 +42,7 @@ export function useJotaiEffect<T>(
42
42
  const store = useStore();
43
43
  useEffect(() => {
44
44
  let prevValue = store.get(atom);
45
- store.sub(atom, () => {
45
+ return store.sub(atom, () => {
46
46
  const value = store.get(atom);
47
47
  effect(value, prevValue);
48
48
  prevValue = value;
@@ -654,19 +654,47 @@ describe("error handling and edge cases", () => {
654
654
  unpatch();
655
655
  });
656
656
 
657
- it("should handle malformed URLs gracefully", async () => {
658
- const virtualFiles = {};
659
- const unpatch = patchFetch(virtualFiles);
657
+ it("forwards relative URLs and options to the original fetch", async () => {
658
+ const response = new Response("relative content");
659
+ const originalFetch = vi.spyOn(window, "fetch").mockResolvedValue(response);
660
+ const unpatch = patchFetch({});
661
+ const options = { headers: { Accept: "text/plain" } };
662
+
663
+ try {
664
+ expect(await window.fetch("relative-file.txt", options)).toBe(response);
665
+ expect(originalFetch).toHaveBeenCalledExactlyOnceWith(
666
+ "relative-file.txt",
667
+ options,
668
+ );
669
+ } finally {
670
+ unpatch();
671
+ originalFetch.mockRestore();
672
+ }
673
+ });
660
674
 
661
- // Mock Logger.error to avoid test output
675
+ it("preserves the original fetch rejection for malformed URLs", async () => {
676
+ const error = new TypeError("Invalid URL");
677
+ const originalFetch = vi.spyOn(window, "fetch").mockRejectedValue(error);
662
678
  const loggerSpy = vi
663
679
  .spyOn(console, "error")
664
680
  .mockImplementation(Functions.NOOP);
665
-
666
- // This should catch the error and fallback to original fetch
667
- await window.fetch("not-a-valid-url").catch(() => undefined);
668
-
669
- unpatch();
670
- loggerSpy.mockRestore();
681
+ const unpatch = patchFetch({});
682
+ const options = { headers: { Accept: "text/plain" } };
683
+
684
+ try {
685
+ await expect(window.fetch("http://[", options)).rejects.toBe(error);
686
+ expect(originalFetch).toHaveBeenCalledExactlyOnceWith(
687
+ "http://[",
688
+ options,
689
+ );
690
+ expect(loggerSpy).toHaveBeenCalledWith(
691
+ "Error parsing URL",
692
+ expect.any(TypeError),
693
+ );
694
+ } finally {
695
+ unpatch();
696
+ originalFetch.mockRestore();
697
+ loggerSpy.mockRestore();
698
+ }
671
699
  });
672
700
  });
@@ -125,6 +125,8 @@ function withoutLeadingDot(path: string): string {
125
125
  /**
126
126
  * Resolve a URL to a blob URL if it's a virtual file, for use with dynamic import().
127
127
  * Unlike fetch, import() can't be patched, so we need to convert data URLs to blob URLs.
128
+ * The caller owns a returned URL that differs from the input and must revoke it
129
+ * once no module consumers or their cleanup callbacks need it.
128
130
  *
129
131
  * @returns The original URL if not a virtual file, or a blob URL if it is
130
132
  */