@marimo-team/islands 0.25.3-dev141 → 0.25.3-dev143

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 (472) hide show
  1. package/dist/{ConnectedDataExplorerComponent-CNSxdKyJ.js → ConnectedDataExplorerComponent-BAiSVaHj.js} +17 -15
  2. package/dist/{ErrorBoundary-Cg59O3xL.js → ErrorBoundary-BRBKqg2U.js} +16 -16
  3. package/dist/{abnfDiagram-VCTEODGH-DQDjNXt3.js → abnfDiagram-VCTEODGH-BBHN92-F.js} +2 -2
  4. package/dist/any-language-editor-3E24SnCV.js +75 -0
  5. package/dist/{architectureDiagram-5GKGNRK7-Nmls7Wxn.js → architectureDiagram-5GKGNRK7-C0Rd64qh.js} +1 -1
  6. package/dist/{arrays-CN1djC4k.js → arrays-R6JsImiT.js} +8 -0
  7. package/dist/assets/__vite-browser-external-BgQNn4dS.js +1 -0
  8. package/dist/assets/worker-FKe3RHT4.js +113 -0
  9. package/dist/{button-CvjkwdSk.js → button-BQRdqQi1.js} +199 -192
  10. package/dist/{capabilities-CQ5IjhzL.js → capabilities-CI4xUPsp.js} +2 -2
  11. package/dist/{chat-ui-BDpymeoe.js → chat-ui-CvB8Map3.js} +1195 -1195
  12. package/dist/{check-EDwSwP89.js → check-SpOMoCLP.js} +1 -1
  13. package/dist/{chunk-IMKFNOWR-CoIFEo-1.js → chunk-IMKFNOWR-hJXey5p2.js} +1 -1
  14. package/dist/{chunk-SHT3W25Y-lHiGx3ZC.js → chunk-SHT3W25Y-JMzTmmVW.js} +1 -1
  15. package/dist/{chunk-SVP7TREG-BvsHIrY_.js → chunk-SVP7TREG-BIyoHTXi.js} +1 -1
  16. package/dist/{chunk-TICWLB2K-BC8YIm_n.js → chunk-TICWLB2K-CCzH2Uwa.js} +1 -1
  17. package/dist/{chunk-YOKDWASO-DPZk8vhS.js → chunk-YOKDWASO-DKplTrBY.js} +20 -20
  18. package/dist/{classDiagram-ZZMXUADV-Bt98lkkv.js → classDiagram-ZZMXUADV-DB1kOPS6.js} +2 -2
  19. package/dist/{classDiagram-v2-VYDZK3BY-D9bfbcC3.js → classDiagram-v2-VYDZK3BY-CPaFL0OB.js} +2 -2
  20. package/dist/{code-visibility-Da3iitn_.js → code-visibility-CAgcNzoX.js} +17 -17
  21. package/dist/{common-C6FHcu5p.js → common-BovIV1xq.js} +6341 -5870
  22. package/dist/{copy-icon-Ft7t9bqz.js → copy-icon-C1trdvVt.js} +3 -3
  23. package/dist/{cynefinDiagram-5FMLGOSQ-CgWra7wt.js → cynefinDiagram-5FMLGOSQ-CF93uliH.js} +1 -1
  24. package/dist/{dagre-GXQ25YYZ-CFZ8-35c.js → dagre-GXQ25YYZ-BBB-_p9D.js} +1 -1
  25. package/dist/{data-grid-overlay-editor-mJt3Pg4S.js → data-grid-overlay-editor-CBsr8Yo0.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-lCNQrW7d.js → diagram-S7CK7UJ4-BynCnbyt.js} +1 -1
  28. package/dist/{diagram-UQ7AKVKN-CQPQ6pP_.js → diagram-UQ7AKVKN-svOxikMa.js} +1 -1
  29. package/dist/{diagram-VX7I27RA-Cf3b0llz.js → diagram-VX7I27RA-BVQSnPx0.js} +1 -1
  30. package/dist/{diagram-Z3DM3KII-Bt4QLrpn.js → diagram-Z3DM3KII-nR1J0BH5.js} +1 -1
  31. package/dist/{dialog-CEBFZNyc.js → dialog-DgeqyrEZ.js} +3 -3
  32. package/dist/{dist-DpBpA_n1.js → dist-Lm-D-Vr6.js} +3 -3
  33. package/dist/{dropdown-menu-CP_qCYHC.js → dropdown-menu-GWcGCcxE.js} +1277 -1229
  34. package/dist/{ebnfDiagram-PWID7BFC-D3NAyPFY.js → ebnfDiagram-PWID7BFC-C9tq9-Qo.js} +2 -2
  35. package/dist/{erDiagram-RLTQ6QDP-Tlu09_Nm.js → erDiagram-RLTQ6QDP-BiaeB7Fi.js} +1 -1
  36. package/dist/{error-banner-Da600cwz.js → error-banner-D40pmfQQ.js} +86 -81
  37. package/dist/{cells-C8iCYvLU.js → error-entries-CmFDQ9CA.js} +1718 -1621
  38. package/dist/{feedback-button-DaPwHIdM.js → feedback-button-B_9o6DdV.js} +122 -123
  39. package/dist/{flowDiagram-HODETNUW-CAYbpJmV.js → flowDiagram-HODETNUW-f-sLm87k.js} +2 -2
  40. package/dist/{formats-BGh2thRc.js → formats-CCxaLPNs.js} +3 -3
  41. package/dist/{ganttDiagram-EL5Y4UJY-BQT16JSc.js → ganttDiagram-EL5Y4UJY-Dh_4cBJZ.js} +2 -2
  42. package/dist/{glide-data-editor-Ctz2mH6k.js → glide-data-editor-i7BEAfhs.js} +370 -368
  43. package/dist/{glide-utils-DJyqh0EM.js → glide-utils-Dz_F4pAY.js} +16 -16
  44. package/dist/{globals-EpFoRj_E.js → globals-BDzA_Re9.js} +3 -3
  45. package/dist/{highlighted-body-KPVGNVTW-zwjq21tf.js → highlighted-body-KPVGNVTW-3Ziwfz-V.js} +2 -2
  46. package/dist/{html-to-image-CZY1pIZH.js → html-to-image-BrC8OJcj.js} +634 -597
  47. package/dist/{infoDiagram-27XIBGKW-XrDxhr0x.js → infoDiagram-27XIBGKW-BHrWywBe.js} +1 -1
  48. package/dist/{ishikawaDiagram-5VMMS53U-CD1muzov.js → ishikawaDiagram-5VMMS53U-Da4s0Reg.js} +1 -1
  49. package/dist/{kanban-definition-UXKFOSKX-Dg9W142_.js → kanban-definition-UXKFOSKX-DDf1GVLj.js} +1 -1
  50. package/dist/{label-BDKF3Afc.js → label-D9_-zSg-.js} +3 -3
  51. package/dist/{linear-Cnaq3Tfb.js → linear-DWERh7eI.js} +121 -73
  52. package/dist/{loader-BQOfjTIJ.js → loader-DOVB0Syl.js} +12 -12
  53. package/dist/main.js +7548 -7226
  54. package/dist/{mermaid-CiAXjLpA.js → mermaid-ClwTt7OD.js} +38 -38
  55. package/dist/mermaid-HWGCJPDP-Du3-HPQA.js +2 -0
  56. package/dist/{mindmap-definition-YA3MSWOX-DgCidgYJ.js → mindmap-definition-YA3MSWOX-D60oMZtT.js} +1 -1
  57. package/dist/{number-overlay-editor-CtxrummL.js → number-overlay-editor-D9WjQlOL.js} +3 -3
  58. package/dist/{objects-CzUgoMIF.js → objects-DCmMvZtn.js} +1 -3
  59. package/dist/{once-BtE-wKRL.js → once-eG8Vmvjd.js} +1 -1
  60. package/dist/{pegDiagram-XKGWAZYB-coOYYKB6.js → pegDiagram-XKGWAZYB-B9wFpMga.js} +2 -2
  61. package/dist/{pieDiagram-E7YTZNPT-BnXTc3bj.js → pieDiagram-E7YTZNPT-BZwvMuBT.js} +1 -1
  62. package/dist/precisionRound-DhAzeRcm.js +49 -0
  63. package/dist/{process-output-e4jj9Ijt.js → process-output-Bw-vzyoy.js} +4 -4
  64. package/dist/{quadrantDiagram-AXDQQJYC-Ccw0xMjN.js → quadrantDiagram-AXDQQJYC-Blrm7V7_.js} +1 -1
  65. package/dist/{railroadDiagram-O6MQD6OU-B28-u83n.js → railroadDiagram-O6MQD6OU-DqhZGsN2.js} +2 -2
  66. package/dist/{react-vega-BED0DjiB.js → react-vega-C4T9Rb_w.js} +12284 -12270
  67. package/dist/{requirementDiagram-BXWQKSXE-CdYCTMu0.js → requirementDiagram-BXWQKSXE-CL5dYARV.js} +1 -1
  68. package/dist/{reveal-component-DrIXvR_7.js → reveal-component-BxP2jlPx.js} +14 -14
  69. package/dist/{select-BButFi-d.js → select-CkF_GBxO.js} +537 -458
  70. package/dist/{sequenceDiagram-WJ2MYXX4-CM3aRAe_.js → sequenceDiagram-WJ2MYXX4-BSUCRd2x.js} +1 -1
  71. package/dist/{spec-BwarBpUJ.js → spec-DiSh_qJC.js} +41 -11
  72. package/dist/{stateDiagram-D77RDMKH-DOZUMVj8.js → stateDiagram-D77RDMKH-B-1Hvyb0.js} +2 -2
  73. package/dist/{stateDiagram-v2-MP3YSRHH-CISYxma6.js → stateDiagram-v2-MP3YSRHH--HtiR4yB.js} +2 -2
  74. package/dist/style.css +1 -1
  75. package/dist/{swimlanes-42K2YHIH-BkSppxp8.js → swimlanes-42K2YHIH-Bdfhrd-2.js} +1 -1
  76. package/dist/{swimlanesDiagram-VR7AAH4N-c8G5pGxC.js → swimlanesDiagram-VR7AAH4N-BJIfxSz0.js} +2 -2
  77. package/dist/{swiper-component-DKntyn8p.js → swiper-component-BkCm-kLg.js} +2 -2
  78. package/dist/{time-BieKwdYS.js → time-BaMbJs4z.js} +18 -18
  79. package/dist/{timeline-definition-24CTP7MA-CaMQayqK.js → timeline-definition-24CTP7MA-Cs4EfFD3.js} +1 -1
  80. package/dist/{toDate-BNJf01u1.js → toDate-DGvRHwTk.js} +57 -54
  81. package/dist/{toast-B9UMEi8u.js → toast-Btbve3Lm.js} +231 -213
  82. package/dist/{tooltip-BO1REbqs.js → tooltip-CJ4Rqasp.js} +327 -303
  83. package/dist/{use-restore-focus-BEM7sFWl.js → use-restore-focus-CmGwRNId.js} +1482 -1433
  84. package/dist/{useAsyncData-Deh5kszT.js → useAsyncData-BmVgnMP5.js} +15 -14
  85. package/dist/{useDateFormatter-D8XruNGG.js → useDateFormatter-eCL_SdeG.js} +2 -2
  86. package/dist/{useDeepCompareMemoize-DXwzRINI.js → useDeepCompareMemoize-Ch_AeGu6.js} +1 -1
  87. package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities-CSdWn6NP.js} +1 -1
  88. package/dist/{useLifecycle-j7D9uD1o.js → useLifecycle-B5BX921H.js} +3 -3
  89. package/dist/{useTheme-CRnjkku0.js → useTheme-Bq9pi_vz.js} +6 -6
  90. package/dist/{vega-component-DG29TAB7.js → vega-component-BgyZ-COt.js} +147 -147
  91. package/dist/{vega-embed-container-BNHE18G9.js → vega-embed-container--LYZqguO.js} +2 -2
  92. package/dist/{vega-loader.browser-DNST6uVf.js → vega-loader.browser-B9Pz0EGP.js} +298 -507
  93. package/dist/{vennDiagram-4TSXK5OY-CF8pfaK3.js → vennDiagram-4TSXK5OY-DRu0vRjO.js} +1 -1
  94. package/dist/{wardleyDiagram-VM6X3IG4-dGPu39d-.js → wardleyDiagram-VM6X3IG4-DzbDxwDY.js} +1 -1
  95. package/dist/{xychartDiagram-S5SC5T6Z-CP6-9q56.js → xychartDiagram-S5SC5T6Z-CLWj6ZFQ.js} +2 -2
  96. package/package.json +15 -12
  97. package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
  98. package/src/__tests__/host-imports.test.ts +101 -0
  99. package/src/__tests__/module-entry.test.ts +32 -0
  100. package/src/__tests__/mount.test.ts +8 -8
  101. package/src/components/ai/__tests__/ai-model-dropdown.test.tsx +63 -0
  102. package/src/components/ai/ai-model-dropdown.tsx +11 -6
  103. package/src/components/app-config/ai-config.tsx +7 -2
  104. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  105. package/src/components/chat/acp/__tests__/session-tabs.test.tsx +43 -0
  106. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  107. package/src/components/chat/acp/blocks.tsx +3 -2
  108. package/src/components/chat/acp/session-tabs.tsx +8 -3
  109. package/src/components/chat/chat-components.tsx +2 -0
  110. package/src/components/chat/chat-panel.tsx +9 -8
  111. package/src/components/chat/chat-utils.ts +4 -8
  112. package/src/components/data-table/TableTopBar.tsx +2 -0
  113. package/src/components/data-table/__tests__/chart-spec-model.test.ts +195 -195
  114. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  115. package/src/components/data-table/__tests__/columns.test.tsx +105 -0
  116. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  117. package/src/components/data-table/__tests__/context-menu.test.ts +176 -0
  118. package/src/components/data-table/__tests__/data-table.test.tsx +1 -0
  119. package/src/components/data-table/__tests__/date-filter-inputs.test.tsx +24 -1
  120. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  121. package/src/components/data-table/__tests__/export-actions.test.tsx +434 -6
  122. package/src/components/data-table/__tests__/keyboard-selection.test.tsx +188 -0
  123. package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
  124. package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
  125. package/src/components/data-table/cell-selection/feature.ts +2 -2
  126. package/src/components/data-table/cell-selection/types.ts +2 -2
  127. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
  128. package/src/components/data-table/charts/__tests__/spec-snapshot.test.ts +20 -20
  129. package/src/components/data-table/charts/__tests__/spec.test.ts +78 -52
  130. package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
  131. package/src/components/data-table/charts/chart-spec/spec.ts +68 -43
  132. package/src/components/data-table/charts/charts.tsx +40 -30
  133. package/src/components/data-table/charts/storage.ts +16 -5
  134. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  135. package/src/components/data-table/column-summary/chart-spec-model.tsx +23 -16
  136. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  137. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  138. package/src/components/data-table/columns.tsx +45 -46
  139. package/src/components/data-table/context-menu.tsx +161 -46
  140. package/src/components/data-table/data-table.tsx +13 -1
  141. package/src/components/data-table/date-filter-inputs.tsx +15 -42
  142. package/src/components/data-table/date-popover.tsx +6 -4
  143. package/src/components/data-table/export-actions.tsx +267 -163
  144. package/src/components/data-table/range-focus/atoms.ts +4 -6
  145. package/src/components/data-table/range-focus/use-cell-range-selection.ts +65 -38
  146. package/src/components/data-table/range-focus/use-scroll-into-view.ts +21 -2
  147. package/src/components/data-table/renderers.tsx +16 -7
  148. package/src/components/data-table/row-viewer-panel/__tests__/row-viewer.test.tsx +24 -1
  149. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +4 -4
  150. package/src/components/data-table/schemas.ts +14 -36
  151. package/src/components/data-table/search-highlight.tsx +79 -0
  152. package/src/components/data-table/types.ts +1 -0
  153. package/src/components/data-table/url-detector.tsx +49 -9
  154. package/src/components/data-table/utils.ts +18 -0
  155. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  156. package/src/components/datasources/__tests__/column-preview.test.tsx +98 -5
  157. package/src/components/datasources/__tests__/datasources-interactions.test.tsx +181 -0
  158. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  159. package/src/components/datasources/__tests__/utils.test.ts +57 -6
  160. package/src/components/datasources/column-preview.tsx +13 -4
  161. package/src/components/datasources/datasources.tsx +51 -39
  162. package/src/components/datasources/utils.ts +32 -23
  163. package/src/components/debugger/debugger-code.tsx +5 -0
  164. package/src/components/dependency-graph/dependency-graph-tree.tsx +8 -8
  165. package/src/components/dependency-graph/elements.ts +24 -21
  166. package/src/components/dependency-graph/panels.tsx +2 -1
  167. package/src/components/dependency-graph/utils/__tests__/layout.test.ts +18 -11
  168. package/src/components/editor/Output.tsx +9 -2
  169. package/src/components/editor/SortableCell.tsx +1 -3
  170. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  171. package/src/components/editor/__tests__/package-alert.test.tsx +22 -1
  172. package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +80 -252
  173. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +210 -34
  174. package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
  175. package/src/components/editor/actions/pair-with-agent-modal.tsx +100 -37
  176. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  177. package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
  178. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  179. package/src/components/editor/cell/CreateCellButton.tsx +8 -10
  180. package/src/components/editor/cell/DeleteButton.tsx +1 -0
  181. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  182. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  183. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  184. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  185. package/src/components/editor/cell/collapse.tsx +5 -3
  186. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  187. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
  188. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  189. package/src/components/editor/chrome/panels/error-panel.tsx +1 -0
  190. package/src/components/editor/chrome/panels/outline/__tests__/outline-keyboard.test.tsx +84 -0
  191. package/src/components/editor/chrome/panels/outline/floating-outline.tsx +39 -14
  192. package/src/components/editor/chrome/panels/outline/useActiveOutline.tsx +3 -2
  193. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  194. package/src/components/editor/chrome/panels/secrets-panel.tsx +1 -0
  195. package/src/components/editor/chrome/panels/snippets-panel.tsx +5 -1
  196. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +366 -0
  197. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +58 -1
  198. package/src/components/editor/chrome/wrapper/footer-item.tsx +32 -24
  199. package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +237 -0
  200. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  201. package/src/components/editor/chrome/wrapper/footer.tsx +2 -0
  202. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
  203. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  204. package/src/components/editor/connections/components.tsx +1 -1
  205. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  206. package/src/components/editor/controls/__tests__/keyboard-shortcuts.test.tsx +61 -0
  207. package/src/components/editor/controls/command-palette.tsx +22 -10
  208. package/src/components/editor/controls/keyboard-shortcuts.tsx +14 -5
  209. package/src/components/editor/dynamic-favicon.tsx +21 -40
  210. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +537 -0
  211. package/src/components/editor/errors/send-error-report-button.tsx +69 -0
  212. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  213. package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +47 -0
  214. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  215. package/src/components/editor/file-tree/file-explorer.tsx +45 -8
  216. package/src/components/editor/file-tree/file-name-input.tsx +1 -0
  217. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  218. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  219. package/src/components/editor/header/status.tsx +28 -5
  220. package/src/components/editor/links/__tests__/cell-link.test.tsx +146 -0
  221. package/src/components/editor/links/cell-link.tsx +14 -5
  222. package/src/components/editor/navigation/focus-utils.ts +4 -4
  223. package/src/components/editor/notebook-banner.tsx +1 -0
  224. package/src/components/editor/notebook-cell.tsx +11 -2
  225. package/src/components/editor/output/MarimoErrorOutput.tsx +21 -5
  226. package/src/components/editor/output/MarimoTracebackOutput.tsx +54 -37
  227. package/src/components/editor/output/__tests__/ansi-reduce.test.ts +18 -0
  228. package/src/components/editor/output/__tests__/traceback.test.tsx +27 -1
  229. package/src/components/editor/output/ansi-reduce.ts +36 -17
  230. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  231. package/src/components/editor/package-alert.tsx +14 -7
  232. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +12 -6
  233. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  234. package/src/components/home/components.tsx +17 -31
  235. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  236. package/src/components/icons/large-spinner.tsx +3 -14
  237. package/src/components/icons/spinner.tsx +1 -1
  238. package/src/components/mcp/mcp-status-indicator.tsx +1 -1
  239. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  240. package/src/components/pages/home-page.tsx +164 -91
  241. package/src/components/scratchpad/scratchpad.test.tsx +95 -0
  242. package/src/components/scratchpad/scratchpad.tsx +16 -3
  243. package/src/components/slides/reveal-component.tsx +4 -2
  244. package/src/components/slides/slide-cell-view.tsx +1 -1
  245. package/src/components/slides/slide-form.tsx +1 -0
  246. package/src/components/tracing/tracing-keyboard.test.tsx +85 -0
  247. package/src/components/tracing/tracing.tsx +45 -41
  248. package/src/components/ui/__tests__/draggable-popover.test.tsx +24 -0
  249. package/src/components/ui/__tests__/range-slider.test.tsx +165 -0
  250. package/src/components/ui/alert-dialog.tsx +1 -1
  251. package/src/components/ui/draggable-popover.tsx +24 -2
  252. package/src/components/ui/range-slider.tsx +22 -9
  253. package/src/core/ai/context/providers/datasource.ts +6 -2
  254. package/src/core/ai/tools/base.ts +16 -7
  255. package/src/core/ai/tools/edit-notebook-tool.ts +47 -45
  256. package/src/core/ai/tools/registry.ts +13 -8
  257. package/src/core/ai/tools/sample-tool.ts +6 -7
  258. package/src/core/attachments/__tests__/toast.test.tsx +58 -0
  259. package/src/core/attachments/state.ts +18 -0
  260. package/src/core/attachments/toast.ts +15 -0
  261. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -0
  262. package/src/core/cells/__tests__/cell.test.ts +39 -0
  263. package/src/core/cells/__tests__/cells.test.ts +55 -1
  264. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
  265. package/src/core/cells/__tests__/error-report.test.ts +139 -0
  266. package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
  267. package/src/core/cells/__tests__/session.test.ts +167 -37
  268. package/src/core/cells/cell.ts +14 -3
  269. package/src/core/cells/cells.ts +3 -1
  270. package/src/core/cells/error-report.ts +63 -0
  271. package/src/core/cells/pending-delete-service.ts +2 -6
  272. package/src/core/cells/session.ts +29 -2
  273. package/src/core/cells/types.ts +10 -1
  274. package/src/core/codemirror/__tests__/setup.test.ts +29 -29
  275. package/src/core/codemirror/cells/__tests__/extensions.test.ts +15 -0
  276. package/src/core/codemirror/cells/extensions.ts +7 -3
  277. package/src/core/codemirror/keymaps/vim.ts +2 -6
  278. package/src/core/codemirror/language/__tests__/sql.test.ts +22 -20
  279. package/src/core/codemirror/language/extension.ts +8 -8
  280. package/src/core/codemirror/language/languages/markdown.ts +8 -6
  281. package/src/core/codemirror/language/languages/python.ts +16 -10
  282. package/src/core/codemirror/language/languages/sql/sql.ts +9 -7
  283. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  284. package/src/core/codemirror/language/types.ts +7 -7
  285. package/src/core/codemirror/rtc/extension.ts +24 -12
  286. package/src/core/codemirror/rtc/loro/awareness.ts +34 -19
  287. package/src/core/codemirror/rtc/loro/sync.ts +1 -0
  288. package/src/core/codemirror/utils.ts +1 -0
  289. package/src/core/config/pair.ts +5 -3
  290. package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
  291. package/src/core/datasets/data-source-discovery.ts +4 -6
  292. package/src/core/document/__tests__/codemirror.test.ts +485 -0
  293. package/src/core/document/__tests__/draft.test.ts +322 -0
  294. package/src/core/document/__tests__/fixtures.ts +92 -0
  295. package/src/core/document/__tests__/hooks.test.tsx +386 -0
  296. package/src/core/document/__tests__/lifecycle-scenarios.test.ts +61 -0
  297. package/src/core/document/__tests__/model.test.ts +191 -0
  298. package/src/core/document/__tests__/protocol-fixtures.ts +38 -0
  299. package/src/core/document/__tests__/recovery-scenarios.test.ts +230 -0
  300. package/src/core/document/__tests__/scenario-fixture.ts +43 -0
  301. package/src/core/document/__tests__/scenarios.test.ts +205 -0
  302. package/src/core/document/__tests__/state.test.ts +438 -0
  303. package/src/core/document/__tests__/submission.test.ts +203 -0
  304. package/src/core/document/__tests__/test-host.test.ts +92 -0
  305. package/src/core/document/__tests__/test-host.ts +247 -0
  306. package/src/core/document/__tests__/test-notebook.ts +149 -0
  307. package/src/core/document/__tests__/user.ts +61 -0
  308. package/src/core/document/codemirror.ts +311 -0
  309. package/src/core/document/draft.ts +472 -0
  310. package/src/core/document/hooks.tsx +118 -0
  311. package/src/core/document/index.ts +15 -0
  312. package/src/core/document/model.ts +55 -0
  313. package/src/core/document/select.ts +41 -0
  314. package/src/core/document/state.ts +374 -0
  315. package/src/core/document/sync.ts +369 -0
  316. package/src/core/edit-app.tsx +2 -2
  317. package/src/core/host/AGENTS.md +12 -0
  318. package/src/core/host/__tests__/attachments.test.ts +56 -0
  319. package/src/core/host/__tests__/document.test.ts +126 -0
  320. package/src/core/host/__tests__/fixtures.ts +79 -0
  321. package/src/core/host/__tests__/replica.test.ts +550 -0
  322. package/src/core/host/__tests__/static.test.ts +175 -0
  323. package/src/core/host/__tests__/wasm.test.ts +166 -0
  324. package/src/core/host/backend.ts +19 -0
  325. package/src/core/host/connection.ts +42 -0
  326. package/src/core/host/index.ts +14 -0
  327. package/src/core/host/replica.ts +286 -0
  328. package/src/core/host/resources/attachments.ts +22 -0
  329. package/src/core/host/resources/document.ts +101 -0
  330. package/src/core/host/static.ts +77 -0
  331. package/src/core/host/wasm.ts +97 -0
  332. package/src/core/hotkeys/actions.ts +2 -4
  333. package/src/core/islands/__tests__/bootstrap.test.ts +17 -17
  334. package/src/core/islands/__tests__/bridge.test.ts +31 -26
  335. package/src/core/islands/bootstrap.ts +22 -51
  336. package/src/core/islands/bridge.ts +31 -20
  337. package/src/core/islands/components/__tests__/IslandControls.test.tsx +7 -5
  338. package/src/core/islands/worker/__tests__/controller.test.ts +62 -4
  339. package/src/core/islands/worker/controller.ts +4 -10
  340. package/src/core/islands/worker/worker.tsx +33 -33
  341. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  342. package/src/core/network/connection.ts +4 -2
  343. package/src/core/network/useConnectionNotice.ts +8 -2
  344. package/src/core/run-app.tsx +2 -2
  345. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  346. package/src/core/runtime/__tests__/runtime.test.ts +33 -15
  347. package/src/core/runtime/runtime.ts +39 -30
  348. package/src/core/saving/save-component.tsx +12 -3
  349. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  350. package/src/core/state/jotai.ts +1 -1
  351. package/src/core/static/__tests__/files.test.ts +38 -10
  352. package/src/core/static/files.ts +2 -0
  353. package/src/core/wasm/__tests__/bridge.test.ts +213 -50
  354. package/src/core/wasm/__tests__/worker.test.ts +187 -0
  355. package/src/core/wasm/bridge.ts +81 -52
  356. package/src/core/wasm/rpc.ts +15 -3
  357. package/src/core/wasm/store.ts +3 -3
  358. package/src/core/wasm/worker/bootstrap.ts +55 -51
  359. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  360. package/src/core/wasm/worker/save-worker.ts +9 -10
  361. package/src/core/wasm/worker/types.ts +29 -18
  362. package/src/core/wasm/worker/worker.ts +97 -44
  363. package/src/core/websocket/__tests__/backend.test.ts +235 -0
  364. package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -29
  365. package/src/core/websocket/__tests__/useNotebookReplica.hook.test.tsx +141 -0
  366. package/src/core/websocket/__tests__/{useMarimoKernelConnection.hook.test.tsx → useNotebookSession.hook.test.tsx} +197 -184
  367. package/src/core/websocket/backend.ts +129 -0
  368. package/src/core/websocket/connection-state.ts +60 -0
  369. package/src/core/websocket/state.ts +7 -0
  370. package/src/core/websocket/transports/__tests__/notebook.test.ts +405 -0
  371. package/src/core/websocket/transports/notebook.ts +159 -0
  372. package/src/core/websocket/types.ts +9 -39
  373. package/src/core/websocket/useNotebookReplica.ts +69 -0
  374. package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +63 -228
  375. package/src/css/globals.css +5 -0
  376. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  377. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  378. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  379. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  380. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  381. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  382. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  383. package/src/hooks/useAsyncData.ts +3 -3
  384. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  385. package/src/hooks/useDebounce.ts +6 -10
  386. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  387. package/src/hooks/useDelayElapsed.ts +14 -10
  388. package/src/hooks/useElapsedTime.ts +3 -3
  389. package/src/hooks/useEventListener.ts +1 -2
  390. package/src/hooks/useInputHistory.ts +4 -7
  391. package/src/hooks/useInternalStateWithSync.ts +6 -6
  392. package/src/hooks/useResizeObserver.ts +4 -7
  393. package/src/hooks/useScript.ts +67 -70
  394. package/src/hooks/useStateForKey.ts +93 -0
  395. package/src/mount.tsx +3 -3
  396. package/src/plugins/__tests__/plugin-object-schemas.test.ts +104 -0
  397. package/src/plugins/core/registerReactComponent.tsx +1 -1
  398. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  399. package/src/plugins/impl/DataEditorPlugin.tsx +1 -1
  400. package/src/plugins/impl/DataTablePlugin.tsx +29 -33
  401. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  402. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  403. package/src/plugins/impl/FormPlugin.tsx +1 -1
  404. package/src/plugins/impl/RangeSliderPlugin.tsx +16 -2
  405. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  406. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  407. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +201 -1
  408. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  409. package/src/plugins/impl/__tests__/RangeSliderPlugin.test.tsx +109 -0
  410. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  411. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
  412. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  413. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  414. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  415. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  416. package/src/plugins/impl/chat/chat-ui.tsx +14 -11
  417. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  418. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  419. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  420. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  421. package/src/plugins/impl/data-explorer/components/column-summary.tsx +4 -2
  422. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  423. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -2
  424. package/src/plugins/impl/data-frames/__tests__/sidebar.test.tsx +160 -0
  425. package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +66 -66
  426. package/src/plugins/impl/data-frames/panel.tsx +43 -16
  427. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  428. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  429. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  430. package/src/plugins/impl/panel/utils.ts +17 -0
  431. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +2 -5
  432. package/src/plugins/impl/plotly/selection.ts +25 -19
  433. package/src/plugins/impl/vega/VegaPlugin.tsx +2 -3
  434. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  435. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  436. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  437. package/src/stories/button.stories.tsx +1 -0
  438. package/src/stories/editor.stories.tsx +24 -17
  439. package/src/stories/form-wrapper.stories.tsx +2 -2
  440. package/src/stories/select.stories.tsx +2 -2
  441. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  442. package/src/utils/__tests__/arrays.test.ts +35 -0
  443. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  444. package/src/utils/__tests__/dates.test.ts +116 -0
  445. package/src/utils/__tests__/numbers.test.ts +105 -1
  446. package/src/utils/__tests__/objects.test.ts +21 -0
  447. package/src/utils/arrays.ts +10 -0
  448. package/src/utils/blob.ts +1 -1
  449. package/src/utils/dates.ts +94 -0
  450. package/src/utils/numbers.ts +79 -18
  451. package/src/utils/objects.ts +3 -7
  452. package/src/utils/once.ts +19 -19
  453. package/src/utils/tracer.ts +9 -5
  454. package/src/utils/zod-utils.ts +1 -3
  455. package/dist/any-language-editor-B9REGlD9.js +0 -76
  456. package/dist/assets/__vite-browser-external-BXYkiFsk.js +0 -1
  457. package/dist/assets/worker-HPY9fbhz.js +0 -114
  458. package/dist/error-entries-CzoYkIQZ.js +0 -56
  459. package/dist/mermaid-HWGCJPDP-ByCsZnYF.js +0 -2
  460. package/dist/precisionRound-DMIVQrt2.js +0 -97
  461. package/src/core/state/__tests__/jotai.test.ts +0 -25
  462. package/src/core/wasm/worker/message-buffer.ts +0 -33
  463. package/src/core/websocket/__tests__/useWebSocket.test.ts +0 -49
  464. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  465. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -338
  466. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  467. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  468. package/src/core/websocket/transports/basic.ts +0 -102
  469. package/src/core/websocket/transports/sse.ts +0 -308
  470. package/src/core/websocket/transports/transport.ts +0 -61
  471. package/src/core/websocket/transports/ws.ts +0 -98
  472. package/src/core/websocket/useWebSocket.tsx +0 -88
@@ -0,0 +1,159 @@
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
+ });
@@ -1,47 +1,17 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
+ import type { ConnectionStatus as HostConnectionStatus } from "../host";
3
4
  import type { NotificationMessageData } from "../kernel/messages";
4
5
 
6
+ export {
7
+ ConnectionState,
8
+ ConnectionClosedReason as WebSocketClosedReason,
9
+ } from "../host";
10
+
5
11
  export type ConnectionPhase =
6
12
  | NotificationMessageData<"startup-progress">["phase"]
7
13
  | "reconnecting";
8
14
 
9
- export const ConnectionState = {
10
- NOT_STARTED: "NOT_STARTED",
11
- CONNECTING: "CONNECTING",
12
- OPEN: "OPEN",
13
- CLOSING: "CLOSING",
14
- CLOSED: "CLOSED",
15
- } as const;
16
-
17
- export type ConnectionState =
18
- (typeof ConnectionState)[keyof typeof ConnectionState];
19
-
20
- export const WebSocketClosedReason = {
21
- KERNEL_DISCONNECTED: "KERNEL_DISCONNECTED",
22
- KERNEL_STARTUP_ERROR: "KERNEL_STARTUP_ERROR",
23
- } as const;
24
-
25
- export type WebSocketClosedReason =
26
- (typeof WebSocketClosedReason)[keyof typeof WebSocketClosedReason];
27
-
28
- export type ConnectionStatus =
29
- | {
30
- state: typeof ConnectionState.CLOSED;
31
- code: WebSocketClosedReason;
32
- /**
33
- * Human-readable reason for closing the connection.
34
- */
35
- reason: string;
36
- phase?: ConnectionPhase;
37
- }
38
- | {
39
- state: typeof ConnectionState.CONNECTING;
40
- phase?: ConnectionPhase;
41
- }
42
- | {
43
- state:
44
- | typeof ConnectionState.OPEN
45
- | typeof ConnectionState.CLOSING
46
- | typeof ConnectionState.NOT_STARTED;
47
- };
15
+ export type ConnectionStatus = HostConnectionStatus & {
16
+ phase?: ConnectionPhase;
17
+ };
@@ -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
+ }
@@ -1,8 +1,8 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import { invalidatePackageData } from "@/core/packages/package-data";
4
- import { useAtom, useAtomValue, useSetAtom } from "jotai";
5
- import { useRef } from "react";
4
+ import { useAtom, useAtomValue, useSetAtom, useStore } from "jotai";
5
+ import { useLayoutEffect } from "react";
6
6
  import { useErrorBoundary } from "react-error-boundary";
7
7
  import { toast } from "@/components/ui/use-toast";
8
8
  import { getNotebook, useCellActions } from "@/core/cells/cells";
@@ -12,8 +12,7 @@ import type {
12
12
  NotificationMessageData,
13
13
  NotificationPayload,
14
14
  } from "@/core/kernel/messages";
15
- import { TRANSPORT_EXHAUSTED_REASON } from "@/core/websocket/transports/ws";
16
- import { useConnectionTransport } from "@/core/websocket/useWebSocket";
15
+ import { useNotebookReplica } from "@/core/websocket/useNotebookReplica";
17
16
  import { renderHTML } from "@/plugins/core/RenderHTML";
18
17
  import {
19
18
  handleWidgetMessage,
@@ -21,11 +20,7 @@ import {
21
20
  } from "@/plugins/impl/anywidget/registry";
22
21
  import { logNever } from "@/utils/assertNever";
23
22
  import { prettyError } from "@/utils/errors";
24
- import {
25
- type JsonString,
26
- safeExtractSetUIElementMessageBuffers,
27
- } from "@/utils/json/base64";
28
- import { jsonParseWithSpecialChar } from "@/utils/json/json-parser";
23
+ import { safeExtractSetUIElementMessageBuffers } from "@/utils/json/base64";
29
24
  import { Logger } from "@/utils/Logger";
30
25
  import { reloadSafe } from "@/utils/reload-safe";
31
26
  import { useAlertActions } from "../alerts/state";
@@ -80,105 +75,10 @@ import {
80
75
  import { useStorageActions } from "../storage/state";
81
76
  import { useVariablesActions } from "../variables/state";
82
77
  import type { VariableName } from "../variables/types";
83
- import { isWasm } from "../wasm/utils";
84
- import {
85
- type ConnectionStatus,
86
- WebSocketClosedReason,
87
- ConnectionState,
88
- } from "./types";
78
+ import { WebSocketClosedReason, ConnectionState } from "./types";
89
79
 
90
80
  const SUPPORTS_LAZY_KERNELS = true;
91
81
 
92
- // All MARIMO_* reasons except TRANSPORT_EXHAUSTED are emitted by the backend
93
- // (marimo/_server/api/endpoints/ws_endpoint.py and ws/*.py). Keep in sync with
94
- // the backend literals. Over the SSE transport these arrive as in-band
95
- // `close` events rather than WebSocket close frames, so every terminal
96
- // reason must be classified here — an unclassified reason falls into the
97
- // retry path.
98
- export type CloseReason =
99
- | "MARIMO_NO_FILE_KEY"
100
- | "MARIMO_NO_SESSION_ID"
101
- | "MARIMO_NO_SESSION"
102
- | "MARIMO_SHUTDOWN"
103
- | "MARIMO_KERNEL_STARTUP_ERROR"
104
- | "MARIMO_UNAUTHORIZED"
105
- | "MARIMO_KIOSK_NOT_ALLOWED"
106
- | typeof TRANSPORT_EXHAUSTED_REASON;
107
-
108
- export type CloseDecision =
109
- | { kind: "terminal"; status: ConnectionStatus; closeTransport: boolean }
110
- | { kind: "gave-up"; status: ConnectionStatus }
111
- | { kind: "retry"; status: ConnectionStatus };
112
-
113
- export function classifyCloseEvent(event: { reason?: string }): CloseDecision {
114
- switch (event.reason as CloseReason | undefined) {
115
- case TRANSPORT_EXHAUSTED_REASON:
116
- return {
117
- kind: "gave-up",
118
- status: {
119
- state: ConnectionState.CLOSED,
120
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
121
- reason: "kernel not found",
122
- },
123
- };
124
- case "MARIMO_NO_FILE_KEY":
125
- case "MARIMO_NO_SESSION_ID":
126
- case "MARIMO_NO_SESSION":
127
- case "MARIMO_SHUTDOWN":
128
- return {
129
- kind: "terminal",
130
- status: {
131
- state: ConnectionState.CLOSED,
132
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
133
- reason: "kernel not found",
134
- },
135
- closeTransport: true,
136
- };
137
- case "MARIMO_UNAUTHORIZED":
138
- return {
139
- kind: "terminal",
140
- status: {
141
- state: ConnectionState.CLOSED,
142
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
143
- reason: "not authorized",
144
- },
145
- closeTransport: true,
146
- };
147
- case "MARIMO_KIOSK_NOT_ALLOWED":
148
- return {
149
- kind: "terminal",
150
- status: {
151
- state: ConnectionState.CLOSED,
152
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
153
- reason: "kiosk mode is not available for this session",
154
- },
155
- closeTransport: true,
156
- };
157
- case "MARIMO_KERNEL_STARTUP_ERROR":
158
- return {
159
- kind: "terminal",
160
- status: {
161
- state: ConnectionState.CLOSED,
162
- code: WebSocketClosedReason.KERNEL_STARTUP_ERROR,
163
- reason: "Failed to start kernel sandbox",
164
- },
165
- closeTransport: true,
166
- };
167
- default:
168
- // Empty/undefined reasons are normal transient closes. Anything else is
169
- // an unknown server reason; warn so a new MARIMO_* reason doesn't fall
170
- // silently into the retry path.
171
- if (event.reason) {
172
- logNever(event.reason as never);
173
- }
174
- }
175
-
176
- return {
177
- kind: "retry",
178
- status: { state: ConnectionState.CONNECTING },
179
- };
180
- }
181
-
182
82
  function getExistingCells(): CellData[] | undefined {
183
83
  if (!SUPPORTS_LAZY_KERNELS) {
184
84
  return undefined;
@@ -190,16 +90,12 @@ function getExistingCells(): CellData[] | undefined {
190
90
  );
191
91
  }
192
92
 
193
- /**
194
- * Creates a connection to the Marimo kernel and handles incoming messages.
195
- */
196
- export function useMarimoKernelConnection(opts: {
93
+ /** Coordinate session startup and apply notifications to the editor. */
94
+ export function useNotebookSession(opts: {
197
95
  sessionId: SessionId;
198
96
  autoInstantiate: boolean;
199
97
  setCells: (cells: CellData[], layout: LayoutState) => void;
200
98
  }) {
201
- // Track whether we want to try reconnecting.
202
- const shouldTryReconnecting = useRef<boolean>(true);
203
99
  const { autoInstantiate, sessionId, setCells } = opts;
204
100
  const { showBoundary } = useErrorBoundary();
205
101
 
@@ -246,8 +142,7 @@ export function useMarimoKernelConnection(opts: {
246
142
  filterFromVariables: filterStorageFromVariables,
247
143
  } = useStorageActions();
248
144
 
249
- const handleMessage = (e: MessageEvent<JsonString<NotificationPayload>>) => {
250
- const msg = jsonParseWithSpecialChar(e.data);
145
+ const handleNotification = (msg: NotificationPayload) => {
251
146
  switch (msg.data.op) {
252
147
  case "reload":
253
148
  reloadSafe();
@@ -368,13 +263,18 @@ export function useMarimoKernelConnection(opts: {
368
263
  filterStorageFromVariables(variableNames);
369
264
  return;
370
265
  case "variable-values":
371
- setMetadata(
372
- msg.data.variables.map((v) => ({
266
+ setMetadata([
267
+ ...(msg.data.removed ?? []).map((v) => ({
268
+ name: v.name as VariableName,
269
+ dataType: undefined,
270
+ value: undefined,
271
+ })),
272
+ ...msg.data.variables.map((v) => ({
373
273
  name: v.name as VariableName,
374
274
  dataType: v.datatype,
375
275
  value: v.value,
376
276
  })),
377
- );
277
+ ]);
378
278
  return;
379
279
  case "alert":
380
280
  toast({
@@ -505,107 +405,56 @@ export function useMarimoKernelConnection(opts: {
505
405
  }
506
406
  };
507
407
 
508
- const tryReconnecting = (code?: number, reason?: string) => {
509
- // If not properly gated, we could try reconnecting forever if the
510
- // issue is not transient. So we want to try reconnecting only once after an
511
- // open connection is closed.
512
- if (shouldTryReconnecting.current) {
513
- shouldTryReconnecting.current = false;
514
- ws.reconnect(code, reason);
515
- }
516
- };
517
-
518
- // Manual reconnect. Probes /health first to fail fast when the runtime
519
- // is unreachable, instead of waiting on partysocket's retry budget.
520
- const reconnect = async () => {
521
- if (
522
- ws.readyState === WebSocket.OPEN ||
523
- ws.readyState === WebSocket.CONNECTING
524
- ) {
525
- return;
526
- }
527
- shouldTryReconnecting.current = true;
528
- setKernelStartupError(null);
529
- setConnection({ state: ConnectionState.CONNECTING });
530
- const healthy = await runtimeManager.reconcileFromHealth();
531
- if (!healthy) {
532
- shouldTryReconnecting.current = false;
533
- setConnection({
534
- state: ConnectionState.CLOSED,
535
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
536
- reason: "kernel not found",
537
- });
538
- return;
539
- }
540
- ws.reconnect();
408
+ const store = useStore();
409
+ const onMessageError = (error: unknown) => {
410
+ toast({
411
+ title: "Failed to handle message",
412
+ description: prettyError(error),
413
+ variant: "danger",
414
+ });
541
415
  };
542
-
543
- const ws = useConnectionTransport({
416
+ const replica = useNotebookReplica({
544
417
  static: isStaticNotebook(),
545
418
  transportType,
546
- /**
547
- * Unique URL for this session.
548
- */
549
419
  url: () =>
550
420
  transportType === "sse"
551
421
  ? runtimeManager.getSseURL(sessionId).toString()
552
422
  : runtimeManager.getWsURL(sessionId).toString(),
553
- /**
554
- * Auth headers for the SSE transport. WebSockets cannot send headers
555
- * and instead put the access token in the URL.
556
- */
423
+ // SSE uses headers for auth. WebSockets carry the token in the URL.
557
424
  headers: () => runtimeManager.headers(),
558
-
559
- /**
560
- * The transport is open; kernel-ready establishes session readiness.
561
- */
562
- onOpen: async () => {
563
- updateStartupProgress(null);
564
- // If we are open, we can reset our reconnecting flag.
565
- shouldTryReconnecting.current = true;
566
- },
567
-
568
- /**
569
- * Wait to connect, in case the remote kernel still starting up.
570
- */
571
- waitToConnect: async () => {
572
- if (isStaticNotebook()) {
573
- return;
574
- }
575
- if (isWasm()) {
576
- return;
577
- }
578
-
579
- if (runtimeManager.isSameOrigin) {
580
- return;
425
+ notebookUrl: (notebookId) => runtimeManager.getNotebookURL(notebookId),
426
+ // Only the host's transports read the URL, so static and WASM never wait.
427
+ waitUntilReady: async () => {
428
+ if (!runtimeManager.isSameOrigin) {
429
+ await runtimeManager.waitForHealthy();
581
430
  }
582
- await runtimeManager.waitForHealthy();
583
431
  },
584
-
585
- /**
586
- * Handle messages sent by the kernel.
587
- */
588
- onMessage: (e) => {
432
+ checkHealth: () => runtimeManager.reconcileFromHealth(),
433
+ onNotification: (notification) => {
589
434
  try {
590
- handleMessage(e);
435
+ handleNotification(notification);
591
436
  } catch (error) {
592
- Logger.error("Failed to handle message", e.data, error);
593
- toast({
594
- title: "Failed to handle message",
595
- description: prettyError(error),
596
- variant: "danger",
597
- });
437
+ Logger.error("Failed to handle notification", notification, error);
438
+ onMessageError(error);
598
439
  }
599
440
  },
441
+ });
600
442
 
601
- /**
602
- * Handle a close event. We may want to reconnect.
603
- */
604
- onClose: (e) => {
605
- Logger.warn("WebSocket closed", e.code, e.reason);
606
- const decision = classifyCloseEvent(e);
443
+ // Subscribe before passive connection setup: static/WASM transports can emit synchronously.
444
+ useLayoutEffect(() => {
445
+ const updateConnection = () => {
446
+ const status = store.get(replica.connectionAtom);
447
+ if (status.state === ConnectionState.NOT_STARTED) {
448
+ return;
449
+ }
450
+ if (status.state === ConnectionState.OPEN) {
451
+ updateStartupProgress(null);
452
+ return; // kernel-ready, not the socket, establishes session readiness.
453
+ }
454
+ if (status.state === ConnectionState.CONNECTING) {
455
+ setKernelStartupError(null);
456
+ }
607
457
  setConnection((previous) => {
608
- const status = decision.status;
609
458
  if (
610
459
  status.state === ConnectionState.CLOSED &&
611
460
  status.code === WebSocketClosedReason.KERNEL_STARTUP_ERROR &&
@@ -624,31 +473,17 @@ export function useMarimoKernelConnection(opts: {
624
473
  }
625
474
  return status;
626
475
  });
627
- if (decision.kind === "terminal" && decision.closeTransport) {
628
- ws.close(); // close to prevent reconnecting
629
- return;
630
- }
631
- if (decision.kind === "retry") {
632
- // Session should be valid
633
- // - browser tab might have been closed or re-opened
634
- // - computer might have just woken from sleep
635
- tryReconnecting(e.code, e.reason);
636
- }
637
- },
638
-
639
- /**
640
- * When we encounter an error, we should close the connection.
641
- */
642
- onError: (e) => {
643
- Logger.warn("WebSocket error", e);
644
- setConnection({
645
- state: ConnectionState.CLOSED,
646
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
647
- reason: "kernel not found",
648
- });
649
- tryReconnecting();
650
- },
651
- });
652
-
653
- return { connection, reconnect };
476
+ };
477
+ const unsubscribe = store.sub(replica.connectionAtom, updateConnection);
478
+ updateConnection();
479
+ return unsubscribe;
480
+ }, [
481
+ replica,
482
+ store,
483
+ setConnection,
484
+ setKernelStartupError,
485
+ updateStartupProgress,
486
+ ]);
487
+
488
+ return { replica, connection, reconnect: replica.reconnect };
654
489
  }
@@ -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
  }