@marimo-team/frontend 0.25.3-dev13 → 0.25.3-dev135

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 (568) hide show
  1. package/dist/assets/CellStatus-Bu_cFOaf.js +1 -0
  2. package/dist/assets/{ConnectedDataExplorerComponent-TprnxZeJ.js → ConnectedDataExplorerComponent-BV1mVpaE.js} +1 -1
  3. package/dist/assets/{ErrorBoundary-CM13wrd8.js → ErrorBoundary-Cy4lxM4U.js} +2 -2
  4. package/dist/assets/JsonOutput-Dnd0uzN4.js +11 -0
  5. package/dist/assets/{LazyAnyLanguageCodeMirror-B4fCknq1.js → LazyAnyLanguageCodeMirror-DoxWX_Wp.js} +2 -2
  6. package/dist/assets/MarimoErrorOutput-DWgmERpO.js +10 -0
  7. package/dist/assets/{RenderHTML-DX6i99Op.js → RenderHTML-BuZ8DdVv.js} +2 -2
  8. package/dist/assets/RunButton-BXXDV5w-.js +1 -0
  9. package/dist/assets/__vite-browser-external-BynxqS4w.js +1 -0
  10. package/dist/assets/__vite-browser-external-DTOT9NDN.js +1 -0
  11. package/dist/assets/{add-cell-with-ai-BpD_790A.js → add-cell-with-ai-DYahRMb_.js} +26 -26
  12. package/dist/assets/{add-connection-dialog-B1BXCjex.js → add-connection-dialog-CjRaQbDW.js} +51 -51
  13. package/dist/assets/{agent-panel-CDFUc1uu.js → agent-panel-Cb9PZdDM.js} +4 -4
  14. package/dist/assets/ai-model-dropdown-Bbr2oylS.js +13 -0
  15. package/dist/assets/alert-dialog-8YiYLrfr.js +1 -0
  16. package/dist/assets/any-language-editor-BWmz0P_C.js +1 -0
  17. package/dist/assets/{app-config-button-AdV-QDD7.js → app-config-button-CPNc_---.js} +1 -1
  18. package/dist/assets/arrays-0R2reF2y.js +1 -0
  19. package/dist/assets/{blob-BG8yoQmx.js → blob-CQAkRxR1.js} +1 -1
  20. package/dist/assets/{button-COI-NGrx.js → button-Cl7haVNh.js} +1 -1
  21. package/dist/assets/{cache-panel-DTY1dqvi.js → cache-panel-DuBsA6oO.js} +1 -1
  22. package/dist/assets/{capabilities-BJvf-MD4.js → capabilities-DcB-Dd0T.js} +1 -1
  23. package/dist/assets/cell-editor-CjvWSYDB.js +42 -0
  24. package/dist/assets/cell-link-DIOanEQB.js +1 -0
  25. package/dist/assets/cells-lwP9-xsQ.js +220 -0
  26. package/dist/assets/{chat-display-DhgI2MS9.js → chat-display-DpCQoui-.js} +1 -1
  27. package/dist/assets/chat-panel-C79_pWxF.js +4 -0
  28. package/dist/assets/chat-ui-DOOiaOcb.js +4 -0
  29. package/dist/assets/circle-B0vOU-Dh.js +1 -0
  30. package/dist/assets/{code-visibility-BJImdpDi.js → code-visibility-D1Wf7-vD.js} +1 -1
  31. package/dist/assets/column-preview-DIvrCqra.js +1 -0
  32. package/dist/assets/command-palette-BYgBVslq.js +1 -0
  33. package/dist/assets/{common-Sl3uthjg.js → common-B7U2CeDc.js} +1 -1
  34. package/dist/assets/{components-CskjK6xZ.js → components-9p_w2C6e.js} +1 -1
  35. package/dist/assets/components-B6gDDccL.js +1 -0
  36. package/dist/assets/{components-3cou6Mka.js → components-Vz1pD133.js} +1 -1
  37. package/dist/assets/config-7IjqNuVp.js +1 -0
  38. package/dist/assets/config-BC32AAdw.js +1 -0
  39. package/dist/assets/{config-CA1V-wjh.js → config-CXnC3MRv.js} +1 -1
  40. package/dist/assets/{copy-D8O_CYCG.js → copy-74EjuZso.js} +1 -1
  41. package/dist/assets/{copy-icon-BIiRXh_3.js → copy-icon-Craaocbu.js} +1 -1
  42. package/dist/assets/{createReducer-DgRODTW6.js → createReducer-B04CIO-e.js} +1 -1
  43. package/dist/assets/{datasource-DfbNb83e.js → datasource-Bp8TnG7a.js} +2 -2
  44. package/dist/assets/{dates-CfmdTL6B.js → dates-CaPZVGtm.js} +1 -1
  45. package/dist/assets/{dependency-graph-panel-L5cBE5sl.js → dependency-graph-panel-B31-YcSF.js} +3 -3
  46. package/dist/assets/dist-gaK_BThd.js +1 -0
  47. package/dist/assets/{documentation-panel-D7S8JIc2.js → documentation-panel-C2MJwSef.js} +1 -1
  48. package/dist/assets/download-J6sWXUJR.js +9 -0
  49. package/dist/assets/dropdown-menu-Cwi17BoX.js +1 -0
  50. package/dist/assets/edit-page-DfxHDnWP.js +9 -0
  51. package/dist/assets/{error-banner-COZ7SfYV.js → error-banner-m3mastc-.js} +1 -1
  52. package/dist/assets/{error-entries-CLnPDv7b.js → error-entries-BcRbOxwK.js} +1 -1
  53. package/dist/assets/{error-panel-D2xrYLue.js → error-panel-jZlFCjdy.js} +1 -1
  54. package/dist/assets/{es-Cqst59WX.js → es-Rg27gAEy.js} +1 -1
  55. package/dist/assets/feedback-button-94vZrmBy.js +1 -0
  56. package/dist/assets/{feedback-button-DzPunIqU.js → feedback-button-C2XmZw_y.js} +2 -2
  57. package/dist/assets/file-explorer-panel-Cg9UovsS.js +1 -0
  58. package/dist/assets/{file-name-input-B4fRaQkI.js → file-name-input-y9ggAMGJ.js} +2 -2
  59. package/dist/assets/floating-outline-CE1olGl0.js +1 -0
  60. package/dist/assets/{formats-yZ06h1Pl.js → formats-CYyvbiZA.js} +1 -1
  61. package/dist/assets/{formatting-BqrP-n49.js → formatting-DW2u7HXI.js} +1 -1
  62. package/dist/assets/fullscreen-D-ldV-19.js +41 -0
  63. package/dist/assets/{gallery-page-BefvdsM7.js → gallery-page-ClEnoAT-.js} +1 -1
  64. package/dist/assets/{ganttDiagram-EL5Y4UJY-2n7Ig5Jm.js → ganttDiagram-EL5Y4UJY-CYqkCfFG.js} +1 -1
  65. package/dist/assets/{glide-data-editor-C3eaYlXg.js → glide-data-editor-Cp0q28Zz.js} +2 -2
  66. package/dist/assets/{glide-utils-CIrAxdjv.js → glide-utils-DsTeJ7WA.js} +2 -2
  67. package/dist/assets/home-page-DJsI6Od_.js +1 -0
  68. package/dist/assets/{hooks-DzML9nez.js → hooks-DGSryuSD.js} +1 -1
  69. package/dist/assets/{html-to-image-DK9Zl1lU.js → html-to-image-BW0fcUS8.js} +1 -1
  70. package/dist/assets/index-BjNf54kO.js +30 -0
  71. package/dist/assets/index-u08m0hQE.css +2 -0
  72. package/dist/assets/{input-BoBxR50a.js → input-DLA5Dhn3.js} +2 -2
  73. package/dist/assets/kiosk-mode-Cbxffiz9.js +1 -0
  74. package/dist/assets/{layout-Ck1RoL1B.js → layout-DRVIFud8.js} +5 -5
  75. package/dist/assets/linear-Bma8J3iM.js +1 -0
  76. package/dist/assets/links-UwsB2RA1.js +1 -0
  77. package/dist/assets/{logs-panel-BRytaWaO.js → logs-panel-Db38azhc.js} +1 -1
  78. package/dist/assets/{markdown-renderer-DX0Xrrfy.js → markdown-renderer-D2td9eFc.js} +1 -1
  79. package/dist/assets/menu-items-yD7IV3V4.js +1 -0
  80. package/dist/assets/{mermaid-C4EiVqcd.js → mermaid-DmekSy8J.js} +2 -2
  81. package/dist/assets/{name-cell-input-DlpTvPLa.js → name-cell-input-CJpi0Kvu.js} +1 -1
  82. package/dist/assets/numbers-DJK15_4S.js +1 -0
  83. package/dist/assets/{objects-snlMn4yk.js → objects-rLPlQ3X_.js} +1 -1
  84. package/dist/assets/{once-Haz_uxEL.js → once-6KNTRW0u.js} +1 -1
  85. package/dist/assets/{outline-panel-B2WIpz-1.js → outline-panel-DxQKP4eN.js} +1 -1
  86. package/dist/assets/{package-data-dv4FBHxR.js → package-data-CKPmBDtg.js} +1 -1
  87. package/dist/assets/packages-panel-BFzN4mNb.js +1 -0
  88. package/dist/assets/{panel-accordion-state-BqSH5ttk.js → panel-accordion-state-D3RiRniG.js} +1 -1
  89. package/dist/assets/panels-CpUpLOy4.js +1 -0
  90. package/dist/assets/precisionRound-BxlUs8G4.js +1 -0
  91. package/dist/assets/process-output-B6v1FE29.js +1 -0
  92. package/dist/assets/{quadrantDiagram-AXDQQJYC-yBhCDI7E.js → quadrantDiagram-AXDQQJYC-D9KH5Zge.js} +1 -1
  93. package/dist/assets/react-vega-BTNeboR2.js +189 -0
  94. package/dist/assets/{readonly-code-display-MnMEoYW5.js → readonly-code-display-Deg2hzFh.js} +1 -1
  95. package/dist/assets/{readonly-python-code-ggs4hAzC.js → readonly-python-code-DPGknu49.js} +1 -1
  96. package/dist/assets/{renderShortcut-C1JwYcqW.js → renderShortcut-Bxv9ZLnD.js} +1 -1
  97. package/dist/assets/{requests-lt0JK8Jt.js → requests-BIFR-h4M.js} +1 -1
  98. package/dist/assets/requests-DuLJN9Tf.js +1 -0
  99. package/dist/assets/{reveal-component-PiV1eQk9.js → reveal-component-CXkDpGxy.js} +37 -37
  100. package/dist/assets/run-page-qp2KKYgs.js +1 -0
  101. package/dist/assets/{runs-BLp5jezv.js → runs-C8uOPNAW.js} +1 -1
  102. package/dist/assets/{runtime-details-state-CoL-p0MC.js → runtime-details-state-BiGF299n.js} +1 -1
  103. package/dist/assets/{save-worker-BbUSwZRG.js → save-worker-UwdGcNK5.js} +2 -1
  104. package/dist/assets/scratchpad-panel-QV0lacAe.js +1 -0
  105. package/dist/assets/{scroll-to-bottom-button-1FyowpWV.js → scroll-to-bottom-button-CidjyqiH.js} +1 -1
  106. package/dist/assets/secrets-panel-ConQpchl.js +1 -0
  107. package/dist/assets/select-CgWQvW1F.js +1 -0
  108. package/dist/assets/{session-DqoWmb7X.js → session-BHgGkjDm.js} +1 -1
  109. package/dist/assets/session-panel-CrWluawa.js +1 -0
  110. package/dist/assets/snippets-panel-CM7g9b6c.js +1 -0
  111. package/dist/assets/{spec-BfSBcO5B.js → spec-DFtwOyXT.js} +1 -1
  112. package/dist/assets/state-BYETGwDZ.js +1 -0
  113. package/dist/assets/{state-uwRvJLb8.js → state-CZu2E2Lb.js} +1 -1
  114. package/dist/assets/{state-C3oDoyFe.js → state-ClBFtZxn.js} +1 -1
  115. package/dist/assets/{state-DxQnC_1I.js → state-CrLrURYG.js} +1 -1
  116. package/dist/assets/{state-DtGB7xeJ.js → state-DhMztz1E.js} +1 -1
  117. package/dist/assets/state-lEPoZS4k.js +107 -0
  118. package/dist/assets/{strings-B2vv-ENv.js → strings-3lNX212T.js} +1 -1
  119. package/dist/assets/{swiper-component-a07domrZ.js → swiper-component-BwGv-Pu1.js} +1 -1
  120. package/dist/assets/switch-D2gdC_ep.js +1 -0
  121. package/dist/assets/tabs-BLczoY3k.js +1 -0
  122. package/dist/assets/{terminal-wnSHYT8N.js → terminal-DLgGyFIP.js} +1 -1
  123. package/dist/assets/{time-MgToa8VM.js → time-DswSXBDk.js} +1 -1
  124. package/dist/assets/toast-trUlkvx3.js +1 -0
  125. package/dist/assets/tooltip-B5lAA_jn.js +1 -0
  126. package/dist/assets/tooltip-BiQdpXA0.js +61 -0
  127. package/dist/assets/tracing-DRA8maZB.js +1 -0
  128. package/dist/assets/tracing-panel-DLxPRadV.js +2 -0
  129. package/dist/assets/{tree-actions-DnUokmva.js → tree-actions-COtU5qy_.js} +1 -1
  130. package/dist/assets/use-restore-focus-DMSzeuUr.js +1 -0
  131. package/dist/assets/{useAddCell-IN24thff.js → useAddCell-BAEOA87M.js} +1 -1
  132. package/dist/assets/useAsyncData-qvoPYHtx.js +1 -0
  133. package/dist/assets/useBoolean-BkeUcn1n.js +1 -0
  134. package/dist/assets/useCellActionButton-BobjsJfu.js +1 -0
  135. package/dist/assets/useDebounce-DwTQ_1Pu.js +1 -0
  136. package/dist/assets/{useDeepCompareMemoize-DFIVN9dn.js → useDeepCompareMemoize-B2DVK2De.js} +1 -1
  137. package/dist/assets/useDelayElapsed-BlsDT4TV.js +1 -0
  138. package/dist/assets/{useDeleteCell-fQlM46Y2.js → useDeleteCell-gedazkBF.js} +1 -1
  139. package/dist/assets/{useDependencyPanelTab-ImXYINm_.js → useDependencyPanelTab-DAJ6BOUm.js} +1 -1
  140. package/dist/assets/{useEvent-Dfcbgjhg.js → useEvent-BDjgaBtz.js} +1 -1
  141. package/dist/assets/{useEventListener-D9cftcHj.js → useEventListener-DPOTkksd.js} +1 -1
  142. package/dist/assets/{useIframeCapabilities-CwTkczVN.js → useIframeCapabilities-sHeZt_EA.js} +1 -1
  143. package/dist/assets/{useInstallPackage-BW06eter.js → useInstallPackage-BfNKN33w.js} +1 -1
  144. package/dist/assets/{useInterval--x3s1VpI.js → useInterval-BcdwHHB5.js} +1 -1
  145. package/dist/assets/useNotebookActions-CnLArLoz.js +1 -0
  146. package/dist/assets/{useRunCells-DSJPXE-j.js → useRunCells-CUknep7p.js} +1 -1
  147. package/dist/assets/{useSplitCell-DQyMF5gd.js → useSplitCell-DeXniINv.js} +1 -1
  148. package/dist/assets/useStateForKey-CGMJQT8x.js +1 -0
  149. package/dist/assets/{useTheme-n8Dj1zH7.js → useTheme-C2dnh2vM.js} +1 -1
  150. package/dist/assets/{utils-CaQYvazl.js → utils-DfElfBpz.js} +1 -1
  151. package/dist/assets/vega-component-DJf6ho22.js +1 -0
  152. package/dist/assets/{vega-embed-container-DJPNkHPb.js → vega-embed-container-DtArSJz1.js} +1 -1
  153. package/dist/assets/vega-loader.browser-Bu25TKax.js +6 -0
  154. package/dist/assets/worker-jm2VC7pS.js +159 -0
  155. package/dist/assets/{write-secret-modal-DzKAInL2.js → write-secret-modal-CBi2vNK_.js} +1 -1
  156. package/dist/assets/{xychartDiagram-S5SC5T6Z-KbGzG6-u.js → xychartDiagram-S5SC5T6Z-CeRnBS5e.js} +1 -1
  157. package/dist/index.html +81 -79
  158. package/package.json +14 -14
  159. package/src/__tests__/host-imports.test.ts +1 -0
  160. package/src/__tests__/module-entry.test.ts +32 -0
  161. package/src/__tests__/mount.test.ts +0 -20
  162. package/src/components/ai/__tests__/ai-model-dropdown.test.tsx +63 -0
  163. package/src/components/ai/ai-model-dropdown.tsx +11 -6
  164. package/src/components/app-config/ai-config.tsx +7 -2
  165. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  166. package/src/components/chat/acp/__tests__/session-tabs.test.tsx +43 -0
  167. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  168. package/src/components/chat/acp/blocks.tsx +3 -2
  169. package/src/components/chat/acp/session-tabs.tsx +8 -3
  170. package/src/components/chat/chat-components.tsx +2 -0
  171. package/src/components/chat/chat-panel.tsx +9 -8
  172. package/src/components/chat/chat-utils.ts +4 -8
  173. package/src/components/data-table/TableTopBar.tsx +2 -0
  174. package/src/components/data-table/__tests__/chart-spec-model.test.ts +195 -195
  175. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  176. package/src/components/data-table/__tests__/columns.test.tsx +105 -0
  177. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  178. package/src/components/data-table/__tests__/data-table.test.tsx +1 -0
  179. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  180. package/src/components/data-table/__tests__/export-actions.test.tsx +105 -0
  181. package/src/components/data-table/__tests__/keyboard-selection.test.tsx +188 -0
  182. package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
  183. package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
  184. package/src/components/data-table/cell-selection/feature.ts +2 -2
  185. package/src/components/data-table/cell-selection/types.ts +2 -2
  186. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
  187. package/src/components/data-table/charts/__tests__/spec-snapshot.test.ts +20 -20
  188. package/src/components/data-table/charts/__tests__/spec.test.ts +78 -52
  189. package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
  190. package/src/components/data-table/charts/chart-spec/spec.ts +68 -43
  191. package/src/components/data-table/charts/charts.tsx +40 -30
  192. package/src/components/data-table/charts/storage.ts +16 -5
  193. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  194. package/src/components/data-table/column-summary/chart-spec-model.tsx +23 -16
  195. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  196. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  197. package/src/components/data-table/columns.tsx +45 -46
  198. package/src/components/data-table/context-menu.tsx +36 -20
  199. package/src/components/data-table/data-table.tsx +13 -1
  200. package/src/components/data-table/date-popover.tsx +6 -4
  201. package/src/components/data-table/export-actions.tsx +76 -48
  202. package/src/components/data-table/range-focus/atoms.ts +4 -6
  203. package/src/components/data-table/range-focus/use-cell-range-selection.ts +65 -38
  204. package/src/components/data-table/range-focus/use-scroll-into-view.ts +21 -2
  205. package/src/components/data-table/renderers.tsx +16 -7
  206. package/src/components/data-table/row-viewer-panel/__tests__/row-viewer.test.tsx +24 -1
  207. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +4 -4
  208. package/src/components/data-table/search-highlight.tsx +79 -0
  209. package/src/components/data-table/types.ts +1 -0
  210. package/src/components/data-table/url-detector.tsx +49 -9
  211. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  212. package/src/components/datasources/__tests__/column-preview.test.tsx +98 -5
  213. package/src/components/datasources/__tests__/datasources-interactions.test.tsx +181 -0
  214. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  215. package/src/components/datasources/__tests__/utils.test.ts +57 -6
  216. package/src/components/datasources/column-preview.tsx +13 -4
  217. package/src/components/datasources/datasources.tsx +51 -39
  218. package/src/components/datasources/utils.ts +32 -23
  219. package/src/components/debugger/debugger-code.tsx +5 -0
  220. package/src/components/dependency-graph/dependency-graph-tree.tsx +8 -8
  221. package/src/components/dependency-graph/elements.ts +24 -21
  222. package/src/components/dependency-graph/panels.tsx +2 -1
  223. package/src/components/dependency-graph/utils/__tests__/layout.test.ts +18 -11
  224. package/src/components/editor/SortableCell.tsx +1 -3
  225. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  226. package/src/components/editor/__tests__/package-alert.test.tsx +22 -1
  227. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +24 -0
  228. package/src/components/editor/actions/pair-with-agent-modal.tsx +18 -15
  229. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  230. package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
  231. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  232. package/src/components/editor/cell/CreateCellButton.tsx +8 -10
  233. package/src/components/editor/cell/DeleteButton.tsx +1 -0
  234. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  235. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  236. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  237. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  238. package/src/components/editor/cell/collapse.tsx +5 -3
  239. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  240. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
  241. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  242. package/src/components/editor/chrome/panels/outline/__tests__/outline-keyboard.test.tsx +84 -0
  243. package/src/components/editor/chrome/panels/outline/floating-outline.tsx +39 -14
  244. package/src/components/editor/chrome/panels/outline/useActiveOutline.tsx +3 -2
  245. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  246. package/src/components/editor/chrome/panels/secrets-panel.tsx +1 -0
  247. package/src/components/editor/chrome/panels/snippets-panel.tsx +5 -1
  248. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +12 -18
  249. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +58 -1
  250. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  251. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
  252. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  253. package/src/components/editor/connections/components.tsx +1 -1
  254. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  255. package/src/components/editor/controls/__tests__/keyboard-shortcuts.test.tsx +61 -0
  256. package/src/components/editor/controls/command-palette.tsx +22 -10
  257. package/src/components/editor/controls/keyboard-shortcuts.tsx +14 -5
  258. package/src/components/editor/dynamic-favicon.tsx +21 -40
  259. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +12 -23
  260. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  261. package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +47 -0
  262. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  263. package/src/components/editor/file-tree/file-explorer.tsx +45 -8
  264. package/src/components/editor/file-tree/file-name-input.tsx +1 -0
  265. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  266. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  267. package/src/components/editor/header/status.tsx +28 -5
  268. package/src/components/editor/links/__tests__/cell-link.test.tsx +146 -0
  269. package/src/components/editor/links/cell-link.tsx +14 -5
  270. package/src/components/editor/navigation/focus-utils.ts +4 -4
  271. package/src/components/editor/notebook-banner.tsx +1 -0
  272. package/src/components/editor/notebook-cell.tsx +11 -2
  273. package/src/components/editor/output/MarimoTracebackOutput.tsx +19 -17
  274. package/src/components/editor/output/__tests__/traceback.test.tsx +27 -1
  275. package/src/components/editor/output/ansi-reduce.ts +33 -20
  276. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  277. package/src/components/editor/package-alert.tsx +14 -7
  278. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +12 -6
  279. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  280. package/src/components/home/components.tsx +17 -31
  281. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  282. package/src/components/icons/large-spinner.tsx +3 -14
  283. package/src/components/icons/spinner.tsx +1 -1
  284. package/src/components/mcp/mcp-status-indicator.tsx +1 -1
  285. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  286. package/src/components/pages/home-page.tsx +164 -91
  287. package/src/components/scratchpad/scratchpad.test.tsx +95 -0
  288. package/src/components/scratchpad/scratchpad.tsx +16 -3
  289. package/src/components/slides/reveal-component.tsx +4 -2
  290. package/src/components/slides/slide-cell-view.tsx +1 -1
  291. package/src/components/slides/slide-form.tsx +1 -0
  292. package/src/components/tracing/tracing-keyboard.test.tsx +85 -0
  293. package/src/components/tracing/tracing.tsx +45 -41
  294. package/src/components/ui/__tests__/draggable-popover.test.tsx +24 -0
  295. package/src/components/ui/alert-dialog.tsx +1 -1
  296. package/src/components/ui/draggable-popover.tsx +24 -2
  297. package/src/core/ai/context/providers/datasource.ts +6 -2
  298. package/src/core/ai/tools/base.ts +16 -7
  299. package/src/core/ai/tools/edit-notebook-tool.ts +47 -45
  300. package/src/core/ai/tools/registry.ts +13 -8
  301. package/src/core/ai/tools/sample-tool.ts +6 -7
  302. package/src/core/attachments/state.ts +5 -8
  303. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -0
  304. package/src/core/cells/__tests__/cell.test.ts +39 -0
  305. package/src/core/cells/__tests__/cells.test.ts +3 -1
  306. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
  307. package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
  308. package/src/core/cells/__tests__/session.test.ts +117 -36
  309. package/src/core/cells/cell.ts +14 -3
  310. package/src/core/cells/cells.ts +3 -1
  311. package/src/core/cells/pending-delete-service.ts +2 -6
  312. package/src/core/cells/session.ts +1 -1
  313. package/src/core/cells/types.ts +10 -1
  314. package/src/core/codemirror/__tests__/setup.test.ts +29 -29
  315. package/src/core/codemirror/cells/__tests__/extensions.test.ts +15 -0
  316. package/src/core/codemirror/cells/extensions.ts +7 -3
  317. package/src/core/codemirror/keymaps/vim.ts +2 -6
  318. package/src/core/codemirror/language/__tests__/sql.test.ts +22 -20
  319. package/src/core/codemirror/language/extension.ts +8 -8
  320. package/src/core/codemirror/language/languages/markdown.ts +8 -6
  321. package/src/core/codemirror/language/languages/python.ts +16 -10
  322. package/src/core/codemirror/language/languages/sql/sql.ts +9 -7
  323. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  324. package/src/core/codemirror/language/types.ts +7 -7
  325. package/src/core/codemirror/rtc/extension.ts +24 -12
  326. package/src/core/codemirror/rtc/loro/awareness.ts +34 -19
  327. package/src/core/codemirror/rtc/loro/sync.ts +1 -0
  328. package/src/core/codemirror/utils.ts +1 -0
  329. package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
  330. package/src/core/datasets/data-source-discovery.ts +4 -6
  331. package/src/core/document/__tests__/codemirror.test.ts +485 -0
  332. package/src/core/document/__tests__/draft.test.ts +322 -0
  333. package/src/core/document/__tests__/fixtures.ts +92 -0
  334. package/src/core/document/__tests__/hooks.test.tsx +386 -0
  335. package/src/core/document/__tests__/lifecycle-scenarios.test.ts +61 -0
  336. package/src/core/document/__tests__/model.test.ts +191 -0
  337. package/src/core/document/__tests__/protocol-fixtures.ts +38 -0
  338. package/src/core/document/__tests__/recovery-scenarios.test.ts +230 -0
  339. package/src/core/document/__tests__/scenario-fixture.ts +43 -0
  340. package/src/core/document/__tests__/scenarios.test.ts +205 -0
  341. package/src/core/document/__tests__/state.test.ts +438 -0
  342. package/src/core/document/__tests__/submission.test.ts +203 -0
  343. package/src/core/document/__tests__/test-host.test.ts +92 -0
  344. package/src/core/document/__tests__/test-host.ts +247 -0
  345. package/src/core/document/__tests__/test-notebook.ts +149 -0
  346. package/src/core/document/__tests__/user.ts +61 -0
  347. package/src/core/document/codemirror.ts +311 -0
  348. package/src/core/document/draft.ts +472 -0
  349. package/src/core/document/hooks.tsx +118 -0
  350. package/src/core/document/index.ts +15 -0
  351. package/src/core/document/model.ts +55 -0
  352. package/src/core/document/select.ts +41 -0
  353. package/src/core/document/state.ts +374 -0
  354. package/src/core/document/sync.ts +369 -0
  355. package/src/core/host/AGENTS.md +8 -2
  356. package/src/core/host/__tests__/attachments.test.ts +56 -0
  357. package/src/core/host/__tests__/document.test.ts +126 -0
  358. package/src/core/host/__tests__/fixtures.ts +71 -0
  359. package/src/core/host/__tests__/replica.test.ts +550 -0
  360. package/src/core/host/__tests__/static.test.ts +169 -0
  361. package/src/core/host/__tests__/wasm.test.ts +162 -0
  362. package/src/core/host/backend.ts +19 -0
  363. package/src/core/host/connection.ts +1 -40
  364. package/src/core/host/index.ts +14 -0
  365. package/src/core/host/replica.ts +286 -0
  366. package/src/core/host/resources/attachments.ts +22 -0
  367. package/src/core/host/resources/document.ts +101 -0
  368. package/src/core/host/static.ts +72 -0
  369. package/src/core/host/wasm.ts +102 -0
  370. package/src/core/hotkeys/actions.ts +2 -4
  371. package/src/core/islands/__tests__/bootstrap.test.ts +0 -26
  372. package/src/core/islands/bootstrap.ts +0 -1
  373. package/src/core/islands/components/__tests__/IslandControls.test.tsx +7 -5
  374. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  375. package/src/core/network/connection.ts +4 -2
  376. package/src/core/network/useConnectionNotice.ts +8 -2
  377. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  378. package/src/core/runtime/__tests__/runtime.test.ts +32 -58
  379. package/src/core/runtime/runtime.ts +38 -42
  380. package/src/core/runtime/types.ts +0 -2
  381. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  382. package/src/core/state/jotai.ts +1 -1
  383. package/src/core/static/__tests__/files.test.ts +38 -10
  384. package/src/core/static/files.ts +2 -0
  385. package/src/core/wasm/bridge.ts +38 -10
  386. package/src/core/wasm/store.ts +3 -3
  387. package/src/core/wasm/worker/bootstrap.ts +2 -1
  388. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  389. package/src/core/wasm/worker/types.ts +11 -0
  390. package/src/core/wasm/worker/worker.ts +48 -0
  391. package/src/core/websocket/__tests__/backend.test.ts +200 -0
  392. package/src/core/websocket/__tests__/connection-state.test.ts +0 -12
  393. package/src/core/websocket/__tests__/useNotebookReplica.hook.test.tsx +141 -0
  394. package/src/core/websocket/__tests__/useNotebookSession.hook.test.tsx +154 -428
  395. package/src/core/websocket/backend.ts +106 -0
  396. package/src/core/websocket/connection-state.ts +1 -11
  397. package/src/core/websocket/state.ts +7 -0
  398. package/src/core/websocket/transports/__tests__/notebook.test.ts +445 -0
  399. package/src/core/websocket/transports/notebook.ts +190 -0
  400. package/src/core/websocket/types.ts +2 -2
  401. package/src/core/websocket/useNotebookReplica.ts +69 -0
  402. package/src/core/websocket/useNotebookSession.tsx +10 -22
  403. package/src/css/globals.css +5 -0
  404. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  405. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  406. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  407. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  408. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  409. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  410. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  411. package/src/hooks/useAsyncData.ts +3 -3
  412. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  413. package/src/hooks/useDebounce.ts +6 -10
  414. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  415. package/src/hooks/useDelayElapsed.ts +14 -10
  416. package/src/hooks/useElapsedTime.ts +3 -3
  417. package/src/hooks/useEventListener.ts +1 -2
  418. package/src/hooks/useInputHistory.ts +4 -7
  419. package/src/hooks/useInternalStateWithSync.ts +6 -6
  420. package/src/hooks/useResizeObserver.ts +4 -7
  421. package/src/hooks/useScript.ts +67 -70
  422. package/src/hooks/useStateForKey.ts +93 -0
  423. package/src/mount.tsx +0 -4
  424. package/src/plugins/__tests__/plugin-object-schemas.test.ts +104 -0
  425. package/src/plugins/core/registerReactComponent.tsx +1 -1
  426. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  427. package/src/plugins/impl/DataEditorPlugin.tsx +1 -1
  428. package/src/plugins/impl/DataTablePlugin.tsx +29 -33
  429. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  430. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  431. package/src/plugins/impl/FormPlugin.tsx +1 -1
  432. package/src/plugins/impl/RangeSliderPlugin.tsx +0 -1
  433. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  434. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  435. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +117 -1
  436. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  437. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  438. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
  439. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  440. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  441. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  442. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  443. package/src/plugins/impl/chat/chat-ui.tsx +14 -11
  444. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  445. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  446. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  447. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  448. package/src/plugins/impl/data-explorer/components/column-summary.tsx +4 -2
  449. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  450. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -2
  451. package/src/plugins/impl/data-frames/__tests__/sidebar.test.tsx +160 -0
  452. package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +66 -66
  453. package/src/plugins/impl/data-frames/panel.tsx +43 -16
  454. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  455. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  456. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  457. package/src/plugins/impl/panel/utils.ts +17 -0
  458. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +2 -5
  459. package/src/plugins/impl/plotly/selection.ts +25 -19
  460. package/src/plugins/impl/vega/VegaPlugin.tsx +2 -3
  461. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  462. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  463. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  464. package/src/stories/button.stories.tsx +1 -0
  465. package/src/stories/editor.stories.tsx +24 -17
  466. package/src/stories/form-wrapper.stories.tsx +2 -2
  467. package/src/stories/select.stories.tsx +2 -2
  468. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  469. package/src/utils/__tests__/arrays.test.ts +35 -0
  470. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  471. package/src/utils/__tests__/numbers.test.ts +48 -1
  472. package/src/utils/__tests__/objects.test.ts +21 -0
  473. package/src/utils/arrays.ts +10 -0
  474. package/src/utils/blob.ts +1 -1
  475. package/src/utils/numbers.ts +28 -17
  476. package/src/utils/objects.ts +3 -7
  477. package/src/utils/once.ts +19 -19
  478. package/src/utils/tracer.ts +9 -5
  479. package/src/utils/zod-utils.ts +1 -3
  480. package/dist/assets/CellStatus-DVOHpdeJ.js +0 -1
  481. package/dist/assets/JsonOutput-Cx1iyibB.js +0 -11
  482. package/dist/assets/MarimoErrorOutput-s_5GOX8k.js +0 -10
  483. package/dist/assets/RunButton-BKoh0zYG.js +0 -1
  484. package/dist/assets/__vite-browser-external-CYO_P3S8.js +0 -1
  485. package/dist/assets/__vite-browser-external-FeDVGZU8.js +0 -1
  486. package/dist/assets/ai-model-dropdown-i1vfGeVi.js +0 -13
  487. package/dist/assets/alert-dialog-BaPGzeOK.js +0 -1
  488. package/dist/assets/any-language-editor-BdTWdMCI.js +0 -1
  489. package/dist/assets/arrays-BqUx5USx.js +0 -1
  490. package/dist/assets/cell-editor-BKJ06H5F.js +0 -21
  491. package/dist/assets/cell-link-8Auigi8P.js +0 -1
  492. package/dist/assets/cells-C6YGYGdi.js +0 -220
  493. package/dist/assets/chat-panel-Cau71Ygr.js +0 -4
  494. package/dist/assets/chat-ui-C5ql1Up-.js +0 -4
  495. package/dist/assets/circle-DXf-VWZG.js +0 -1
  496. package/dist/assets/circle-play-CYIb0qT6.js +0 -1
  497. package/dist/assets/column-preview-BZ_iNhtJ.js +0 -1
  498. package/dist/assets/command-palette-DDC3kBet.js +0 -1
  499. package/dist/assets/components-YBmtV0p6.js +0 -1
  500. package/dist/assets/config-DRIpOKqh.js +0 -1
  501. package/dist/assets/config-Dy28fDwn.js +0 -1
  502. package/dist/assets/dist-iaThVDkA.js +0 -1
  503. package/dist/assets/download-C03FOElq.js +0 -9
  504. package/dist/assets/dropdown-menu-C1rnl-0o.js +0 -1
  505. package/dist/assets/edit-page-D_Rg0gQO.js +0 -9
  506. package/dist/assets/feedback-button-Kla7i5RT.js +0 -1
  507. package/dist/assets/file-explorer-panel-Uk4_QIJQ.js +0 -1
  508. package/dist/assets/floating-outline-B5MaM1JS.js +0 -1
  509. package/dist/assets/fullscreen-B9tHcfpV.js +0 -41
  510. package/dist/assets/home-page-hmvKlc1w.js +0 -4
  511. package/dist/assets/index-6b4mf8Ai.css +0 -2
  512. package/dist/assets/index-ptK1gxmo.js +0 -30
  513. package/dist/assets/kiosk-mode-BZECfg9k.js +0 -1
  514. package/dist/assets/linear-0eij_aAG.js +0 -1
  515. package/dist/assets/link-NuFVR7G9.js +0 -1
  516. package/dist/assets/links-DkkL9DVa.js +0 -1
  517. package/dist/assets/menu-items-BrwnTgyK.js +0 -1
  518. package/dist/assets/numbers-Mk3-CcoU.js +0 -1
  519. package/dist/assets/packages-panel-CI5ff9b-.js +0 -1
  520. package/dist/assets/panels-BCcZ_yLK.js +0 -107
  521. package/dist/assets/precisionRound-Bq_nmf5u.js +0 -1
  522. package/dist/assets/process-output-f3C3PD0Z.js +0 -1
  523. package/dist/assets/react-vega-C3rYGtP6.js +0 -189
  524. package/dist/assets/requests-CgSY1X4a.js +0 -1
  525. package/dist/assets/run-page-AfZhtEh1.js +0 -1
  526. package/dist/assets/scratchpad-panel-D2rWg4k8.js +0 -1
  527. package/dist/assets/secrets-panel-D7KN4tVs.js +0 -1
  528. package/dist/assets/select-DTBn5eTb.js +0 -1
  529. package/dist/assets/session-panel-CnANhcTo.js +0 -1
  530. package/dist/assets/snippets-panel-ClGHtEKQ.js +0 -1
  531. package/dist/assets/state-BAJYRhmO.js +0 -1
  532. package/dist/assets/state-CbjBtkdd.js +0 -3
  533. package/dist/assets/switch-CTaiAjrz.js +0 -1
  534. package/dist/assets/tabs-BuQlZFbt.js +0 -1
  535. package/dist/assets/toast-BGzLGnvw.js +0 -1
  536. package/dist/assets/tooltip-C9TuY18o.js +0 -1
  537. package/dist/assets/tooltip-mT3CGest.js +0 -61
  538. package/dist/assets/tracing-Dz2rYOFL.js +0 -1
  539. package/dist/assets/tracing-panel-DqQY9V91.js +0 -2
  540. package/dist/assets/use-restore-focus-BxEGwIbk.js +0 -1
  541. package/dist/assets/useAsyncData-C7aJJz0Y.js +0 -1
  542. package/dist/assets/useBoolean-d0Tty94N.js +0 -1
  543. package/dist/assets/useCellActionButton-DmaFZ4oF.js +0 -1
  544. package/dist/assets/useDebounce-B_ccBZTp.js +0 -1
  545. package/dist/assets/useNotebookActions-DHt3s-ia.js +0 -1
  546. package/dist/assets/vega-component-IouMe13t.js +0 -1
  547. package/dist/assets/vega-loader.browser-CLRkNgm7.js +0 -6
  548. package/dist/assets/worker-DTQ7oH2A.js +0 -158
  549. package/dist/assets/ws-BXWY541V.js +0 -22
  550. package/src/core/host/__tests__/events.test.ts +0 -46
  551. package/src/core/host/__tests__/notebook-client.test.ts +0 -191
  552. package/src/core/host/__tests__/protocol.test.ts +0 -214
  553. package/src/core/host/events.ts +0 -60
  554. package/src/core/host/notebook-client.ts +0 -226
  555. package/src/core/host/protocol.ts +0 -1943
  556. package/src/core/state/__tests__/jotai.test.ts +0 -25
  557. package/src/core/websocket/__tests__/createConnectionTransport.test.ts +0 -49
  558. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +0 -140
  559. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  560. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -335
  561. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  562. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  563. package/src/core/websocket/transports/basic.ts +0 -102
  564. package/src/core/websocket/transports/create.ts +0 -31
  565. package/src/core/websocket/transports/sse.ts +0 -308
  566. package/src/core/websocket/transports/transport.ts +0 -50
  567. package/src/core/websocket/transports/ws.ts +0 -98
  568. package/src/core/websocket/useNotebookClient.ts +0 -80
@@ -0,0 +1,190 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import {
4
+ type NotebookTransport,
5
+ sse,
6
+ StreamStopped,
7
+ type StreamUrl,
8
+ } from "@marimo-team/client";
9
+ import { jsonParseWithSpecialChar } from "@/utils/json/json-parser";
10
+ import { classifyCloseEvent } from "../connection-state";
11
+
12
+ /** The server can be slow to accept when many notebooks are open. */
13
+ const CONNECT_MS = 10_000;
14
+
15
+ /**
16
+ * The error that ends a stream on this close, or none to retry.
17
+ *
18
+ * `cause` is the connection status the editor shows.
19
+ */
20
+ function stopOn(close: { code?: number; reason?: string }) {
21
+ const decision = classifyCloseEvent(close);
22
+ if (decision.kind === "retry") {
23
+ return undefined;
24
+ }
25
+ const reason = "reason" in decision.status ? decision.status.reason : "";
26
+ return new StreamStopped(reason, { cause: decision.status });
27
+ }
28
+
29
+ /** Read a URL provider, stopping the stream when it gives up. */
30
+ async function resolve(url: StreamUrl): Promise<URL | undefined> {
31
+ try {
32
+ return new URL(typeof url === "function" ? await url() : url);
33
+ } catch (error) {
34
+ if (error instanceof StreamStopped) {
35
+ throw error;
36
+ }
37
+ return undefined;
38
+ }
39
+ }
40
+
41
+ /** Send each request with the runtime's headers, read before each request. */
42
+ export function withHeaders(
43
+ headers: () => Record<string, string>,
44
+ ): typeof globalThis.fetch {
45
+ return (input, init) => {
46
+ const sent = new Headers(init?.headers);
47
+ for (const [name, value] of Object.entries(headers())) {
48
+ sent.set(name, value);
49
+ }
50
+ return globalThis.fetch(input, { ...init, headers: sent });
51
+ };
52
+ }
53
+
54
+ /** A resume cursor and replica belong to the session that produced them. */
55
+ function withSessionIdentity(
56
+ url: StreamUrl,
57
+ connect: (target: URL) => NotebookTransport,
58
+ ): NotebookTransport {
59
+ let previous: string | undefined;
60
+ return async (attempt, handlers) => {
61
+ const target = await resolve(url);
62
+ if (target === undefined || attempt.signal.aborted) {
63
+ return;
64
+ }
65
+ target.searchParams.delete("cursor");
66
+ const identity = new URL(target);
67
+ identity.searchParams.delete("access_token");
68
+ identity.searchParams.sort();
69
+ identity.hash = "";
70
+ const key = identity.toString();
71
+ let { cursor } = attempt;
72
+ if (previous !== undefined && previous !== key) {
73
+ // Reset both the stream's cursor and its subscribers' state.
74
+ handlers.message({ event: "reset", data: {} });
75
+ cursor = null;
76
+ }
77
+ previous = key;
78
+ if (!attempt.signal.aborted) {
79
+ return connect(target)({ ...attempt, cursor }, handlers);
80
+ }
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Follow a marimo session over its WebSocket route.
86
+ *
87
+ * Each attempt opens one socket with the cursor as a query parameter. The
88
+ * editor's close policy decides which closes end the stream.
89
+ *
90
+ * TODO: Notice a connection that died without closing, such as after a laptop
91
+ * sleeps. `/sse` sends a heartbeat that its silence limit relies on, and
92
+ * `/ws` drops it. Sending it on `/ws` would let this transport close a
93
+ * silent socket.
94
+ */
95
+ export function websocket(url: StreamUrl): NotebookTransport {
96
+ return withSessionIdentity(
97
+ url,
98
+ (target) =>
99
+ async ({ cursor, signal }, { open, message }) => {
100
+ if (cursor !== null) {
101
+ target.searchParams.set("cursor", cursor);
102
+ }
103
+ await new Promise<void>((settle, reject) => {
104
+ const socket = new WebSocket(target);
105
+ const timeout = setTimeout(() => socket.close(), CONNECT_MS);
106
+ const abort = () => socket.close();
107
+ signal.addEventListener("abort", abort, { once: true });
108
+ socket.addEventListener("open", () => {
109
+ clearTimeout(timeout);
110
+ open();
111
+ });
112
+ socket.addEventListener("message", ({ data }) =>
113
+ // marimo's parser reads `NaN`. A message it cannot read arrives as an
114
+ // empty object, which `NotebookEvents` rejects as not an event.
115
+ message(jsonParseWithSpecialChar(String(data))),
116
+ );
117
+ socket.addEventListener("close", (event) => {
118
+ clearTimeout(timeout);
119
+ signal.removeEventListener("abort", abort);
120
+ const stopped = signal.aborted ? undefined : stopOn(event);
121
+ if (stopped === undefined) {
122
+ settle();
123
+ } else {
124
+ reject(stopped);
125
+ }
126
+ });
127
+ });
128
+ },
129
+ );
130
+ }
131
+
132
+ /**
133
+ * Follow a marimo session over its server-sent events route.
134
+ *
135
+ * The session's `close` frames carry the same reasons as WebSocket closes.
136
+ * marimo's parser decodes tagged bigints and non-finite numbers in every frame.
137
+ */
138
+ export function sessionSse(
139
+ url: StreamUrl,
140
+ headers: () => Record<string, string>,
141
+ ): NotebookTransport {
142
+ return withSessionIdentity(url, (target) =>
143
+ sse(target, {
144
+ parseData: jsonParseWithSpecialChar,
145
+ fetch: withHeaders(headers),
146
+ stop: (frame) =>
147
+ frame.event === "close"
148
+ ? stopOn(frame.data as { code?: number; reason?: string })
149
+ : undefined,
150
+ }),
151
+ );
152
+ }
153
+
154
+ /** A connection that opens and delivers nothing, for static notebooks. */
155
+ export const idle: NotebookTransport = ({ signal }, { open }) =>
156
+ new Promise((settle) => {
157
+ open();
158
+ signal.addEventListener("abort", () => settle(), { once: true });
159
+ });
160
+
161
+ /**
162
+ * Follow the notebook running in the WASM worker.
163
+ *
164
+ * The worker sends marimo notifications as `{ data: notification }`, which
165
+ * arrive as `marimo` events.
166
+ */
167
+ export function pyodide(
168
+ attach: (consumer: (message: MessageEvent<string>) => void) => void,
169
+ ): NotebookTransport {
170
+ return ({ signal }, { open, message }) =>
171
+ new Promise((settle) => {
172
+ open();
173
+ attach(({ data }) => {
174
+ if (signal.aborted) {
175
+ return;
176
+ }
177
+ const value = jsonParseWithSpecialChar(data);
178
+ // The worker sends notifications without the host's event envelope.
179
+ message(
180
+ typeof value === "object" &&
181
+ value !== null &&
182
+ !("event" in value) &&
183
+ "data" in value
184
+ ? { event: "marimo", data: { marimo: value.data } }
185
+ : value,
186
+ );
187
+ });
188
+ signal.addEventListener("abort", () => settle(), { once: true });
189
+ });
190
+ }
@@ -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 "../host";
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 "../host";
10
10
 
11
11
  export type ConnectionPhase =
12
12
  | NotificationMessageData<"startup-progress">["phase"]
@@ -0,0 +1,69 @@
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 { createNotebookReplica, type NotebookReplica } from "../host";
13
+ import { notebookReplicaAtom } from "./state";
14
+ import { createEditorBackend, type EditorBackendOptions } from "./backend";
15
+
16
+ type Options = EditorBackendOptions & {
17
+ onNotification: (notification: NotificationPayload) => void;
18
+ };
19
+
20
+ export function useNotebookReplica(options: Options): NotebookReplica {
21
+ const store = useStore();
22
+ const { static: isStatic, transportType } = options;
23
+ const replica = useMemo(() => createNotebookReplica(store), [store]);
24
+ const url = useEvent(options.url);
25
+ const headers = useEvent(options.headers);
26
+ const notebookUrl = useEvent(options.notebookUrl);
27
+ const waitUntilReady = useEvent(options.waitUntilReady);
28
+ const checkHealth = useEvent(options.checkHealth);
29
+ const onNotification = useEvent(options.onNotification);
30
+ // Opens nothing until the replica follows it.
31
+ const backend = useMemo(
32
+ () =>
33
+ createEditorBackend({
34
+ static: isStatic,
35
+ transportType,
36
+ url,
37
+ headers,
38
+ notebookUrl,
39
+ waitUntilReady,
40
+ checkHealth,
41
+ }),
42
+ [
43
+ isStatic,
44
+ transportType,
45
+ url,
46
+ headers,
47
+ notebookUrl,
48
+ waitUntilReady,
49
+ checkHealth,
50
+ ],
51
+ );
52
+
53
+ useEffect(() => {
54
+ store.set(notebookReplicaAtom, replica);
55
+ const disconnect = replica.connect(backend, {
56
+ // The host validates the envelope, not the native notification contents.
57
+ onNotification: (notification) =>
58
+ onNotification({ data: notification as NotificationMessage }),
59
+ });
60
+ return () => {
61
+ disconnect();
62
+ if (store.get(notebookReplicaAtom) === replica) {
63
+ store.set(notebookReplicaAtom, null);
64
+ }
65
+ };
66
+ }, [replica, backend, store, onNotification]);
67
+
68
+ return replica;
69
+ }
@@ -5,7 +5,6 @@ import { useAtom, useAtomValue, useSetAtom, useStore } from "jotai";
5
5
  import { useLayoutEffect } 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
8
  import { getNotebook, useCellActions } from "@/core/cells/cells";
10
9
  import { applyTransactionChanges } from "@/core/cells/document-changes";
11
10
  import { AUTOCOMPLETER } from "@/core/codemirror/completion/Autocompleter";
@@ -13,7 +12,7 @@ import type {
13
12
  NotificationMessageData,
14
13
  NotificationPayload,
15
14
  } from "@/core/kernel/messages";
16
- import { useNotebookClient } from "@/core/websocket/useNotebookClient";
15
+ import { useNotebookReplica } from "@/core/websocket/useNotebookReplica";
17
16
  import { renderHTML } from "@/plugins/core/RenderHTML";
18
17
  import {
19
18
  handleWidgetMessage,
@@ -66,7 +65,7 @@ import { type LayoutState, useLayoutActions } from "../layout/state";
66
65
  import { kioskModeAtom } from "../mode";
67
66
  import { connectionAtom, startupProgressAtom } from "../network/connection";
68
67
  import type { RequestId } from "../network/DeferredRequestRegistry";
69
- import { runtimeConfigAtom, useRuntimeManager } from "../runtime/config";
68
+ import { useRuntimeManager } from "../runtime/config";
70
69
  import { SECRETS_REGISTRY } from "../secrets/request-registry";
71
70
  import { isStaticNotebook } from "../static/static-state";
72
71
  import {
@@ -76,7 +75,6 @@ import {
76
75
  import { useStorageActions } from "../storage/state";
77
76
  import { useVariablesActions } from "../variables/state";
78
77
  import type { VariableName } from "../variables/types";
79
- import { isWasm } from "../wasm/utils";
80
78
  import { WebSocketClosedReason, ConnectionState } from "./types";
81
79
 
82
80
  const SUPPORTS_LAZY_KERNELS = true;
@@ -136,11 +134,9 @@ export function useNotebookSession(opts: {
136
134
  const setStableSessionId = useSetAtom(stableSessionIdAtom);
137
135
  const setCapabilities = useSetAtom(capabilitiesAtom);
138
136
  const runtimeManager = useRuntimeManager();
139
- const { notebookId } = useAtomValue(runtimeConfigAtom);
140
137
  const transportType = useAtomValue(connectionTransportTypeAtom);
141
138
  const setCacheInfo = useSetAtom(cacheInfoAtom);
142
139
  const setKernelStartupError = useSetAtom(kernelStartupErrorAtom);
143
- const setAttachments = useSetAtom(attachmentsAtom);
144
140
  const {
145
141
  setNamespaces: setStorageNamespaces,
146
142
  filterFromVariables: filterStorageFromVariables,
@@ -148,9 +144,6 @@ export function useNotebookSession(opts: {
148
144
 
149
145
  const handleNotification = (msg: NotificationPayload) => {
150
146
  switch (msg.data.op) {
151
- case "attachments":
152
- setAttachments(msg.data.attachments);
153
- return;
154
147
  case "reload":
155
148
  reloadSafe();
156
149
  return;
@@ -185,7 +178,6 @@ export function useNotebookSession(opts: {
185
178
  existingCells,
186
179
  });
187
180
  if (!msg.data.resumed) {
188
- setAttachments([]);
189
181
  // A freshly started kernel may expose a different environment
190
182
  // (e.g. new env vars); re-run discovery instead of reusing stale
191
183
  // suggestions from the previous kernel instance.
@@ -416,25 +408,23 @@ export function useNotebookSession(opts: {
416
408
  variant: "danger",
417
409
  });
418
410
  };
419
- const notebook = useNotebookClient({
420
- sessionId,
411
+ const replica = useNotebookReplica({
421
412
  static: isStaticNotebook(),
422
413
  transportType,
423
- messageFormat:
424
- notebookId && transportType === "websocket" ? "host" : "session",
425
414
  url: () =>
426
415
  transportType === "sse"
427
416
  ? runtimeManager.getSseURL(sessionId).toString()
428
417
  : runtimeManager.getWsURL(sessionId).toString(),
429
418
  // SSE uses headers for auth. WebSockets carry the token in the URL.
430
419
  headers: () => runtimeManager.headers(),
420
+ notebookUrl: (notebookId) => runtimeManager.getNotebookURL(notebookId),
421
+ // Only the host's transports read the URL, so static and WASM never wait.
431
422
  waitUntilReady: async () => {
432
- if (!isStaticNotebook() && !isWasm() && !runtimeManager.isSameOrigin) {
423
+ if (!runtimeManager.isSameOrigin) {
433
424
  await runtimeManager.waitForHealthy();
434
425
  }
435
426
  },
436
427
  checkHealth: () => runtimeManager.reconcileFromHealth(),
437
- onMessageError,
438
428
  onNotification: (notification) => {
439
429
  try {
440
430
  handleNotification(notification);
@@ -448,11 +438,10 @@ export function useNotebookSession(opts: {
448
438
  // Subscribe before passive connection setup: static/WASM transports can emit synchronously.
449
439
  useLayoutEffect(() => {
450
440
  const updateConnection = () => {
451
- const status = store.get(notebook.connectionAtom);
441
+ const status = store.get(replica.connectionAtom);
452
442
  if (status.state === ConnectionState.NOT_STARTED) {
453
443
  return;
454
444
  }
455
- setAttachments([]);
456
445
  if (status.state === ConnectionState.OPEN) {
457
446
  updateStartupProgress(null);
458
447
  return; // kernel-ready, not the socket, establishes session readiness.
@@ -480,17 +469,16 @@ export function useNotebookSession(opts: {
480
469
  return status;
481
470
  });
482
471
  };
483
- const unsubscribe = store.sub(notebook.connectionAtom, updateConnection);
472
+ const unsubscribe = store.sub(replica.connectionAtom, updateConnection);
484
473
  updateConnection();
485
474
  return unsubscribe;
486
475
  }, [
487
- notebook,
476
+ replica,
488
477
  store,
489
478
  setConnection,
490
- setAttachments,
491
479
  setKernelStartupError,
492
480
  updateStartupProgress,
493
481
  ]);
494
482
 
495
- return { notebook, connection, reconnect: notebook.reconnect };
483
+ return { replica, connection, reconnect: replica.reconnect };
496
484
  }
@@ -75,6 +75,9 @@
75
75
  --ring: hsl(215deg 20.2% 65.1%);
76
76
 
77
77
  /* Semantic colors. */
78
+ --brand: light-dark(#365f43, #a8c9ae);
79
+ --brand-hover: light-dark(#2b5037, #bdd8c2);
80
+ --brand-foreground: light-dark(#fff, #172b1c);
78
81
  --destructive: var(--red-9, #e5484d);
79
82
  --destructive-hover: var(--red-10, #dc3e42);
80
83
  --destructive-border: var(--red-11, #ce2c31);
@@ -89,6 +92,7 @@
89
92
  --action-hover: var(--yellow-10, #ffdc00);
90
93
  --action-border: var(--yellow-11, #9e6c00);
91
94
  --action-foreground: var(--yellow-12, #473b1f);
95
+ --search-highlight: var(--yellow-4, #fff394);
92
96
  --link: light-dark(hsl(211deg 90% 42%), hsl(211deg 90% 62%));
93
97
  --link-visited: light-dark(hsl(272deg 51% 54%), hsl(272deg 51% 74%));
94
98
  --stale: hsl(42deg 56% 44% / 25%);
@@ -137,6 +141,7 @@
137
141
  --action-hover: var(--yellow-7, #665417);
138
142
  --action-border: var(--yellow-11, #f5e147);
139
143
  --action-foreground: var(--yellow-12, #f6eeb4);
144
+ --search-highlight: var(--yellow-6, #524202);
140
145
  --stale: hsl(54deg 100% 86.7% / 25%);
141
146
  }
142
147
  }
@@ -0,0 +1,148 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import { act, render, renderHook, waitFor } from "@testing-library/react";
3
+ import { Activity } from "react";
4
+ import { describe, expect, it, vi } from "vitest";
5
+ import { Deferred } from "@/utils/Deferred";
6
+ import { useAsyncData } from "../useAsyncData";
7
+
8
+ function AsyncDataStatus({ fetch }: { fetch: () => Promise<string> }) {
9
+ const result = useAsyncData(fetch, []);
10
+ return (
11
+ <output
12
+ data-status={result.status}
13
+ data-fetching={result.isFetching}
14
+ data-error={result.error?.message ?? ""}
15
+ >
16
+ {result.data}
17
+ </output>
18
+ );
19
+ }
20
+
21
+ describe("useAsyncData", () => {
22
+ it.each(["success", "error"] as const)(
23
+ "restarts fetching when Activity reopens after %s",
24
+ async (initialStatus) => {
25
+ const initial = new Deferred<string>();
26
+ const restarted = new Deferred<string>();
27
+ const fetch = vi
28
+ .fn(() => restarted.promise)
29
+ .mockImplementationOnce(() => initial.promise);
30
+ const view = render(
31
+ <Activity mode="visible">
32
+ <AsyncDataStatus fetch={fetch} />
33
+ </Activity>,
34
+ );
35
+ await act(async () => {
36
+ if (initialStatus === "success") {
37
+ initial.resolve("first");
38
+ } else {
39
+ initial.reject(new Error("offline"));
40
+ }
41
+ });
42
+ expect(view.getByRole("status").dataset.status).toBe(initialStatus);
43
+
44
+ view.rerender(
45
+ <Activity mode="hidden">
46
+ <AsyncDataStatus fetch={fetch} />
47
+ </Activity>,
48
+ );
49
+ view.rerender(
50
+ <Activity mode="visible">
51
+ <AsyncDataStatus fetch={fetch} />
52
+ </Activity>,
53
+ );
54
+
55
+ expect(fetch).toHaveBeenCalledTimes(2);
56
+ expect(view.getByRole("status").dataset).toMatchObject({
57
+ status: initialStatus === "success" ? "loading" : "pending",
58
+ error: "",
59
+ fetching: "true",
60
+ });
61
+ expect(view.getByRole("status").textContent).toBe(
62
+ initialStatus === "success" ? "first" : "",
63
+ );
64
+ await act(async () => restarted.resolve("latest"));
65
+ expect(view.getByRole("status").dataset).toMatchObject({
66
+ status: "success",
67
+ fetching: "false",
68
+ });
69
+ expect(view.getByRole("status").textContent).toBe("latest");
70
+ },
71
+ );
72
+
73
+ it("ignores a request canceled when Activity hides", async () => {
74
+ const canceled = new Deferred<string>();
75
+ const restarted = new Deferred<string>();
76
+ const fetch = vi
77
+ .fn(() => restarted.promise)
78
+ .mockImplementationOnce(() => canceled.promise);
79
+ const view = render(
80
+ <Activity mode="visible">
81
+ <AsyncDataStatus fetch={fetch} />
82
+ </Activity>,
83
+ );
84
+ view.rerender(
85
+ <Activity mode="hidden">
86
+ <AsyncDataStatus fetch={fetch} />
87
+ </Activity>,
88
+ );
89
+ view.rerender(
90
+ <Activity mode="visible">
91
+ <AsyncDataStatus fetch={fetch} />
92
+ </Activity>,
93
+ );
94
+ expect(fetch).toHaveBeenCalledTimes(2);
95
+ await act(async () => canceled.resolve("obsolete"));
96
+ expect(view.getByRole("status").dataset.status).toBe("pending");
97
+ expect(view.getByRole("status").textContent).toBe("");
98
+ await act(async () => restarted.resolve("latest"));
99
+ expect(view.getByRole("status").dataset.status).toBe("success");
100
+ expect(view.getByRole("status").textContent).toBe("latest");
101
+ });
102
+
103
+ it("preserves data while refetching and ignores obsolete requests", async () => {
104
+ const requests = [
105
+ new Deferred<string>(),
106
+ new Deferred<string>(),
107
+ new Deferred<string>(),
108
+ ];
109
+ const fetch = vi
110
+ .fn(() => requests[0].promise)
111
+ .mockImplementationOnce(() => requests[0].promise)
112
+ .mockImplementationOnce(() => requests[1].promise)
113
+ .mockImplementationOnce(() => requests[2].promise);
114
+ const { result, rerender } = renderHook(
115
+ ({ key }) => useAsyncData(fetch, [key]),
116
+ { initialProps: { key: 0 } },
117
+ );
118
+ expect(result.current.status).toBe("pending");
119
+ await act(async () => requests[0].resolve("first"));
120
+ expect(result.current.data).toBe("first");
121
+ rerender({ key: 1 });
122
+ expect(result.current).toMatchObject({ status: "loading", data: "first" });
123
+ rerender({ key: 2 });
124
+ await act(async () => requests[1].resolve("obsolete"));
125
+ expect(result.current).toMatchObject({ status: "loading", data: "first" });
126
+ await act(async () => requests[2].resolve("latest"));
127
+ expect(result.current).toMatchObject({ status: "success", data: "latest" });
128
+ });
129
+
130
+ it("retries after errors and allows manual updates during a request", async () => {
131
+ const request = new Deferred<string>();
132
+ const fetch = vi
133
+ .fn<() => Promise<string>>()
134
+ .mockRejectedValueOnce(new Error("offline"))
135
+ .mockImplementation(() => request.promise);
136
+ const { result } = renderHook(() => useAsyncData(fetch, []));
137
+ await waitFor(() => expect(result.current.status).toBe("error"));
138
+ act(() => result.current.refetch());
139
+ expect(result.current.status).toBe("pending");
140
+ act(() => result.current.setData("manual"));
141
+ expect(result.current).toMatchObject({ status: "success", data: "manual" });
142
+ await act(async () => request.resolve("response"));
143
+ expect(result.current).toMatchObject({
144
+ status: "success",
145
+ data: "response",
146
+ });
147
+ });
148
+ });
@@ -121,6 +121,57 @@ describe("useDebounceControlledState", () => {
121
121
 
122
122
  expect(onChange).not.toHaveBeenCalled();
123
123
  });
124
+
125
+ it("replaces an uncommitted draft when the external value changes", () => {
126
+ const onChange = vi.fn();
127
+ const { result, rerender } = renderHook(
128
+ ({ value }) =>
129
+ useDebounceControlledState({
130
+ initialValue: value,
131
+ onChange,
132
+ delay: 1000,
133
+ }),
134
+ { initialProps: { value: "initial" } },
135
+ );
136
+
137
+ act(() => result.current.onChange("draft"));
138
+ rerender({ value: "external" });
139
+ expect(result.current.value).toBe("external");
140
+ act(() => vi.runAllTimers());
141
+ expect(result.current.debouncedValue).toBe("external");
142
+ expect(onChange).not.toHaveBeenCalled();
143
+ });
144
+
145
+ it("handles NaN external values without repeatedly resetting the draft", () => {
146
+ const onChange = vi.fn();
147
+ const { result, rerender } = renderHook(
148
+ ({ value }) =>
149
+ useDebounceControlledState({ initialValue: value, onChange }),
150
+ { initialProps: { value: Number.NaN } },
151
+ );
152
+
153
+ expect(result.current.value).toBeNaN();
154
+ act(() => result.current.onChange(1));
155
+ rerender({ value: Number.NaN });
156
+ expect(result.current.value).toBe(1);
157
+ rerender({ value: 2 });
158
+ expect(result.current.value).toBe(2);
159
+ rerender({ value: Number.NaN });
160
+ expect(result.current.value).toBeNaN();
161
+ });
162
+
163
+ it("resets the draft when the external value changes from zero to negative zero", () => {
164
+ const onChange = vi.fn();
165
+ const { result, rerender } = renderHook(
166
+ ({ value }) =>
167
+ useDebounceControlledState({ initialValue: value, onChange }),
168
+ { initialProps: { value: 0 } },
169
+ );
170
+
171
+ act(() => result.current.onChange(1));
172
+ rerender({ value: -0 });
173
+ expect(result.current.value).toBe(-0);
174
+ });
124
175
  });
125
176
 
126
177
  describe("useDebouncedCallback", () => {
@@ -0,0 +1,39 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import { renderHook } from "@testing-library/react";
3
+ import { describe, expect, it } from "vitest";
4
+ import { useDeepCompareMemoize } from "../useDeepCompareMemoize";
5
+
6
+ describe("useDeepCompareMemoize", () => {
7
+ it("keeps equal values stable and updates changed values immediately", () => {
8
+ const initial = { nested: [1, 2] };
9
+ const { result, rerender } = renderHook(
10
+ ({ value }) => useDeepCompareMemoize(value),
11
+ {
12
+ initialProps: { value: initial },
13
+ },
14
+ );
15
+ rerender({ value: { nested: [1, 2] } });
16
+ expect(result.current).toBe(initial);
17
+ const changed = { nested: [1, 3] };
18
+ rerender({ value: changed });
19
+ expect(result.current).toBe(changed);
20
+ rerender({ value: { nested: [1, 3] } });
21
+ expect(result.current).toBe(changed);
22
+ });
23
+
24
+ it("handles inline callbacks without causing additional renders", () => {
25
+ let renders = 0;
26
+ const { result, rerender } = renderHook(
27
+ ({ value }) => {
28
+ renders++;
29
+ return useDeepCompareMemoize({ callback: () => value });
30
+ },
31
+ { initialProps: { value: "initial" } },
32
+ );
33
+
34
+ const initialRenders = renders;
35
+ rerender({ value: "changed" });
36
+ expect(result.current.callback()).toBe("changed");
37
+ expect(renders).toBe(initialRenders + 1);
38
+ });
39
+ });