@marimo-team/frontend 0.25.3-dev125 → 0.25.3-dev127

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 (96) hide show
  1. package/dist/assets/{CellStatus-_Aw9_foI.js → CellStatus-FhctFTd5.js} +1 -1
  2. package/dist/assets/{ErrorBoundary-DxSzjvbx.js → ErrorBoundary-D8cNDEWs.js} +2 -2
  3. package/dist/assets/{JsonOutput-B7pGj79O.js → JsonOutput-CKF7zS56.js} +1 -1
  4. package/dist/assets/{MarimoErrorOutput-Bzo6Bkp5.js → MarimoErrorOutput-BzRg-_e8.js} +1 -1
  5. package/dist/assets/{RenderHTML-D6AC0IhC.js → RenderHTML-BWp2DyZX.js} +1 -1
  6. package/dist/assets/{RunButton-BjYsPiKa.js → RunButton-Bgk4RWmz.js} +1 -1
  7. package/dist/assets/{add-cell-with-ai-CFrR1iW4.js → add-cell-with-ai-DdZy8mLi.js} +1 -1
  8. package/dist/assets/{add-connection-dialog-CyNAB_W2.js → add-connection-dialog-C8jZYKdn.js} +1 -1
  9. package/dist/assets/{agent-panel-VOrccb5q.js → agent-panel-Ds9Pb9Dq.js} +1 -1
  10. package/dist/assets/{ai-model-dropdown-Bfxv_zvu.js → ai-model-dropdown-CBDzZx1M.js} +1 -1
  11. package/dist/assets/{app-config-button-BRcjv8A_.js → app-config-button-D5D_EmY9.js} +1 -1
  12. package/dist/assets/{blob-DTYOX-m5.js → blob-CJNyvsia.js} +1 -1
  13. package/dist/assets/{cell-editor-eKgYK8Bd.js → cell-editor-BTr9z5Wp.js} +1 -1
  14. package/dist/assets/{cell-link-D3rkqgbU.js → cell-link-I5T63SlT.js} +1 -1
  15. package/dist/assets/{cells-2HETnlvE.js → cells-B3LEgMh1.js} +1 -1
  16. package/dist/assets/{chat-display-BrT4VJJK.js → chat-display-H16VjlZr.js} +1 -1
  17. package/dist/assets/{chat-panel-qpPNEwxV.js → chat-panel-DqrzqlCD.js} +1 -1
  18. package/dist/assets/{chat-ui-SAdJxg-k.js → chat-ui-DySzMqSB.js} +1 -1
  19. package/dist/assets/{code-visibility-DYDagVy7.js → code-visibility-E6qbtstz.js} +1 -1
  20. package/dist/assets/{column-preview-yI7-INkZ.js → column-preview-BdBJaVZ4.js} +1 -1
  21. package/dist/assets/{command-palette-DF-B_YpW.js → command-palette-Dc3C7NM6.js} +1 -1
  22. package/dist/assets/{common-DgXKwblm.js → common-Cqs8TPV5.js} +1 -1
  23. package/dist/assets/{components-CuMhY4Sf.js → components-Cwi1jZWr.js} +1 -1
  24. package/dist/assets/{config-CD1IuZFJ.js → config-BTFDx3Zj.js} +1 -1
  25. package/dist/assets/{config-DMz7oN0r.js → config-CqfGrErg.js} +1 -1
  26. package/dist/assets/{datasource-D9JMcEAP.js → datasource-D1_5m7Yd.js} +1 -1
  27. package/dist/assets/{dependency-graph-panel-BVJWPCvw.js → dependency-graph-panel-C4zXeWSh.js} +1 -1
  28. package/dist/assets/{documentation-panel--wI4XHso.js → documentation-panel-mAZeRrD1.js} +1 -1
  29. package/dist/assets/{download-Dod42wfD.js → download-CE9fT9D_.js} +1 -1
  30. package/dist/assets/{edit-page-Uj9YSRMX.js → edit-page-B85w4NTO.js} +4 -4
  31. package/dist/assets/{error-entries-D00nWhWE.js → error-entries-Dx8Rtti7.js} +1 -1
  32. package/dist/assets/{error-panel-CcN_1PWf.js → error-panel-DAXHR0Ch.js} +1 -1
  33. package/dist/assets/feedback-button-BPkAz7n4.js +1 -0
  34. package/dist/assets/{feedback-button-ByGgqUsX.js → feedback-button-C62fC5qU.js} +1 -1
  35. package/dist/assets/{file-explorer-panel-eTEqtMPW.js → file-explorer-panel-BwZcEQr-.js} +1 -1
  36. package/dist/assets/{file-name-input-D5SfTNWh.js → file-name-input-CmwUD7Hx.js} +1 -1
  37. package/dist/assets/{floating-outline-B-k3P_AO.js → floating-outline-PAiDriV4.js} +1 -1
  38. package/dist/assets/{gallery-page-BzbMhDHi.js → gallery-page-C6ayh2RI.js} +1 -1
  39. package/dist/assets/{glide-data-editor-BMqhUrHI.js → glide-data-editor-C4RmQotP.js} +1 -1
  40. package/dist/assets/{home-page-DJcYWiga.js → home-page-DKgFJ5fQ.js} +1 -1
  41. package/dist/assets/{hooks-CGLb_CCz.js → hooks-B3z6BXDe.js} +1 -1
  42. package/dist/assets/{index-bSIORW11.js → index-Cu7PYFi7.js} +6 -6
  43. package/dist/assets/{layout-C2XnLAfO.js → layout-BD9wfU32.js} +3 -3
  44. package/dist/assets/{logs-panel-B3T1GFBk.js → logs-panel-CTdAN6rZ.js} +1 -1
  45. package/dist/assets/{markdown-renderer-BhiTvxtL.js → markdown-renderer-BbXJkH92.js} +1 -1
  46. package/dist/assets/{name-cell-input-CgAObR6N.js → name-cell-input-CmZvZAsE.js} +1 -1
  47. package/dist/assets/{outline-panel-BhBb7Vqw.js → outline-panel-D8aMHyb1.js} +1 -1
  48. package/dist/assets/{packages-panel-Bt0xNpFl.js → packages-panel-_-hESZFB.js} +1 -1
  49. package/dist/assets/{panel-accordion-state-ZsUdpAYn.js → panel-accordion-state-DXo4a0Uu.js} +1 -1
  50. package/dist/assets/panels-RQ6Xn9SS.js +1 -0
  51. package/dist/assets/{process-output-CeOMwE17.js → process-output-BIrwpJB4.js} +1 -1
  52. package/dist/assets/{readonly-code-display-TvOC7ESE.js → readonly-code-display-yuks_SYy.js} +1 -1
  53. package/dist/assets/{readonly-python-code-Cqkfdeyo.js → readonly-python-code-BhBEAP6F.js} +1 -1
  54. package/dist/assets/{reveal-component-CwE6U9oJ.js → reveal-component-rMF_Gs2J.js} +1 -1
  55. package/dist/assets/{run-page-CtonWTtJ.js → run-page-BiotN_kV.js} +1 -1
  56. package/dist/assets/{runtime-details-state-CKqJSfSR.js → runtime-details-state-Bv77N7ch.js} +1 -1
  57. package/dist/assets/{scratchpad-panel-CwVBedfZ.js → scratchpad-panel-DRp6uxVD.js} +1 -1
  58. package/dist/assets/{session-panel-CdHdKUCF.js → session-panel-D_AV0U6M.js} +1 -1
  59. package/dist/assets/{snippets-panel-DYLWOLYw.js → snippets-panel-BPrNsfAG.js} +1 -1
  60. package/dist/assets/{state-DgECPVFs.js → state-Bvy6oc4o.js} +3 -3
  61. package/dist/assets/{state-DZN9vZ6z.js → state-DMO2unyy.js} +1 -1
  62. package/dist/assets/{terminal-Ca-kubKb.js → terminal-B-foRg1j.js} +1 -1
  63. package/dist/assets/{tracing-B0J2ctTm.js → tracing-DglCQA_A.js} +1 -1
  64. package/dist/assets/{tracing-panel-C1OCpoeE.js → tracing-panel-Dig-XuOl.js} +2 -2
  65. package/dist/assets/{tree-actions-sjBxIglc.js → tree-actions-BASXwJd1.js} +1 -1
  66. package/dist/assets/{useAddCell-CaXnFGki.js → useAddCell-bvRpB5dY.js} +1 -1
  67. package/dist/assets/{useBoolean-Dd2nWXFJ.js → useBoolean-rA1CzmYl.js} +1 -1
  68. package/dist/assets/{useCellActionButton-CJ90CMEj.js → useCellActionButton-Cv2-KLlH.js} +1 -1
  69. package/dist/assets/{useDeleteCell-D9N85822.js → useDeleteCell-B2rRUNeJ.js} +1 -1
  70. package/dist/assets/{useDependencyPanelTab-CzAE0cm-.js → useDependencyPanelTab-B8jk4N46.js} +1 -1
  71. package/dist/assets/{useInstallPackage-wcDq9hYT.js → useInstallPackage-DWxYtxn-.js} +1 -1
  72. package/dist/assets/{useNotebookActions-CAMEQ38y.js → useNotebookActions-HhHejFpc.js} +1 -1
  73. package/dist/assets/{useRunCells-DGakx8hj.js → useRunCells-Bz-vP8K6.js} +1 -1
  74. package/dist/assets/{useSplitCell-AK5BKPGM.js → useSplitCell-4bnvSMAx.js} +1 -1
  75. package/dist/assets/{vega-component-BeyxAJad.js → vega-component-D3px3WTM.js} +1 -1
  76. package/dist/index.html +24 -24
  77. package/package.json +1 -1
  78. package/src/core/host/__tests__/notebook-client.test.ts +18 -6
  79. package/src/core/host/__tests__/static.test.ts +4 -2
  80. package/src/core/host/__tests__/wasm.test.ts +12 -10
  81. package/src/core/host/backend.ts +8 -3
  82. package/src/core/host/notebook-client.ts +15 -15
  83. package/src/core/host/static.ts +2 -2
  84. package/src/core/host/wasm.ts +4 -3
  85. package/src/core/runtime/__tests__/runtime.test.ts +17 -0
  86. package/src/core/runtime/runtime.ts +13 -0
  87. package/src/core/websocket/__tests__/backend.test.ts +111 -0
  88. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +32 -27
  89. package/src/core/websocket/__tests__/useNotebookSession.hook.test.tsx +7 -4
  90. package/src/core/websocket/backend.ts +68 -0
  91. package/src/core/websocket/transports/__tests__/notebook.test.ts +9 -56
  92. package/src/core/websocket/transports/notebook.ts +15 -38
  93. package/src/core/websocket/useNotebookClient.ts +6 -6
  94. package/src/core/websocket/useNotebookSession.tsx +3 -2
  95. package/dist/assets/feedback-button-tbWwJ_t6.js +0 -1
  96. package/dist/assets/panels-DJghvIKo.js +0 -1
package/dist/index.html CHANGED
@@ -66,7 +66,7 @@
66
66
  <marimo-server-token data-token="{{ server_token }}" hidden></marimo-server-token>
67
67
  <!-- /TODO -->
68
68
  <title>{{ title }}</title>
69
- <script type="module" crossorigin src="./assets/index-bSIORW11.js"></script>
69
+ <script type="module" crossorigin src="./assets/index-Cu7PYFi7.js"></script>
70
70
  <link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-B0Z9INg1.js">
71
71
  <link rel="modulepreload" crossorigin href="./assets/react-DVplv7_D.js">
72
72
  <link rel="modulepreload" crossorigin href="./assets/compiler-runtime-BrajqUja.js">
@@ -86,7 +86,7 @@
86
86
  <link rel="modulepreload" crossorigin href="./assets/utils-B4K466FK.js">
87
87
  <link rel="modulepreload" crossorigin href="./assets/session-CMLIzZKw.js">
88
88
  <link rel="modulepreload" crossorigin href="./assets/Deferred-CrTnjj37.js">
89
- <link rel="modulepreload" crossorigin href="./assets/config-CD1IuZFJ.js">
89
+ <link rel="modulepreload" crossorigin href="./assets/config-BTFDx3Zj.js">
90
90
  <link rel="modulepreload" crossorigin href="./assets/clsx-DB0hHKMi.js">
91
91
  <link rel="modulepreload" crossorigin href="./assets/cn-ldLVeaqm.js">
92
92
  <link rel="modulepreload" crossorigin href="./assets/jsx-runtime-B74pBk57.js">
@@ -139,55 +139,55 @@
139
139
  <link rel="modulepreload" crossorigin href="./assets/useTheme-BAA27lh7.js">
140
140
  <link rel="modulepreload" crossorigin href="./assets/useAsyncData-qvoPYHtx.js">
141
141
  <link rel="modulepreload" crossorigin href="./assets/purify.es--06brOip.js">
142
- <link rel="modulepreload" crossorigin href="./assets/cells-2HETnlvE.js">
142
+ <link rel="modulepreload" crossorigin href="./assets/cells-B3LEgMh1.js">
143
143
  <link rel="modulepreload" crossorigin href="./assets/get-DjNf4SIX.js">
144
144
  <link rel="modulepreload" crossorigin href="./assets/_baseSet-Bpdo9AvU.js">
145
145
  <link rel="modulepreload" crossorigin href="./assets/errors-CNsgAw6m.js">
146
- <link rel="modulepreload" crossorigin href="./assets/config-DMz7oN0r.js">
146
+ <link rel="modulepreload" crossorigin href="./assets/config-CqfGrErg.js">
147
147
  <link rel="modulepreload" crossorigin href="./assets/state-B_MCKzBL.js">
148
148
  <link rel="modulepreload" crossorigin href="./assets/state-C5WAeuTu.js">
149
149
  <link rel="modulepreload" crossorigin href="./assets/LazyAnyLanguageCodeMirror-BPYE76jR.js">
150
150
  <link rel="modulepreload" crossorigin href="./assets/copy-KlhazlhB.js">
151
151
  <link rel="modulepreload" crossorigin href="./assets/micromark-factory-space-D9Dr-k4J.js">
152
152
  <link rel="modulepreload" crossorigin href="./assets/chunk-YOKDWASO-BIpRENEC.js">
153
- <link rel="modulepreload" crossorigin href="./assets/markdown-renderer-BhiTvxtL.js">
153
+ <link rel="modulepreload" crossorigin href="./assets/markdown-renderer-BbXJkH92.js">
154
154
  <link rel="modulepreload" crossorigin href="./assets/copy-Djr5m5ac.js">
155
155
  <link rel="modulepreload" crossorigin href="./assets/copy-icon-8-a5vovc.js">
156
- <link rel="modulepreload" crossorigin href="./assets/useRunCells-DGakx8hj.js">
157
- <link rel="modulepreload" crossorigin href="./assets/RenderHTML-D6AC0IhC.js">
158
- <link rel="modulepreload" crossorigin href="./assets/blob-DTYOX-m5.js">
156
+ <link rel="modulepreload" crossorigin href="./assets/useRunCells-Bz-vP8K6.js">
157
+ <link rel="modulepreload" crossorigin href="./assets/RenderHTML-BWp2DyZX.js">
158
+ <link rel="modulepreload" crossorigin href="./assets/blob-CJNyvsia.js">
159
159
  <link rel="modulepreload" crossorigin href="./assets/kbd-pdKBv96t.js">
160
- <link rel="modulepreload" crossorigin href="./assets/useInstallPackage-wcDq9hYT.js">
160
+ <link rel="modulepreload" crossorigin href="./assets/useInstallPackage-DWxYtxn-.js">
161
161
  <link rel="modulepreload" crossorigin href="./assets/share-BZW3tzEX.js">
162
162
  <link rel="modulepreload" crossorigin href="./assets/events-CbeVJ6OD.js">
163
- <link rel="modulepreload" crossorigin href="./assets/state-DgECPVFs.js">
163
+ <link rel="modulepreload" crossorigin href="./assets/state-Bvy6oc4o.js">
164
164
  <link rel="modulepreload" crossorigin href="./assets/coerce-DWjFeXOY.js">
165
165
  <link rel="modulepreload" crossorigin href="./assets/_hasPath-BmRQipI0.js">
166
166
  <link rel="modulepreload" crossorigin href="./assets/hasIn-yw4sIeJR.js">
167
167
  <link rel="modulepreload" crossorigin href="./assets/pick-DKIID_Mo.js">
168
- <link rel="modulepreload" crossorigin href="./assets/ErrorBoundary-DxSzjvbx.js">
168
+ <link rel="modulepreload" crossorigin href="./assets/ErrorBoundary-D8cNDEWs.js">
169
169
  <link rel="modulepreload" crossorigin href="./assets/chevron-right-eAtVhbNZ.js">
170
170
  <link rel="modulepreload" crossorigin href="./assets/dropdown-menu-DS6umAnH.js">
171
171
  <link rel="modulepreload" crossorigin href="./assets/html-to-image-D_0rd3_B.js">
172
- <link rel="modulepreload" crossorigin href="./assets/download-Dod42wfD.js">
172
+ <link rel="modulepreload" crossorigin href="./assets/download-CE9fT9D_.js">
173
173
  <link rel="modulepreload" crossorigin href="./assets/switch-DYjBfpNJ.js">
174
174
  <link rel="modulepreload" crossorigin href="./assets/tabs-CY8E4r9U.js">
175
175
  <link rel="modulepreload" crossorigin href="./assets/table-DG7Ap4H4.js">
176
176
  <link rel="modulepreload" crossorigin href="./assets/eye-off-CxVozGAC.js">
177
177
  <link rel="modulepreload" crossorigin href="./assets/refresh-cw-Qw1f8Jhy.js">
178
- <link rel="modulepreload" crossorigin href="./assets/tree-actions-sjBxIglc.js">
178
+ <link rel="modulepreload" crossorigin href="./assets/tree-actions-BASXwJd1.js">
179
179
  <link rel="modulepreload" crossorigin href="./assets/ellipsis-63DyBLHQ.js">
180
180
  <link rel="modulepreload" crossorigin href="./assets/external-link-UrX9KO36.js">
181
181
  <link rel="modulepreload" crossorigin href="./assets/spinner-DyNE-o8o.js">
182
182
  <link rel="modulepreload" crossorigin href="./assets/message-circle-C3rKm8_a.js">
183
183
  <link rel="modulepreload" crossorigin href="./assets/hat-glasses-C8RhSVii.js">
184
184
  <link rel="modulepreload" crossorigin href="./assets/package-GSoSTiIK.js">
185
- <link rel="modulepreload" crossorigin href="./assets/datasource-D9JMcEAP.js">
186
- <link rel="modulepreload" crossorigin href="./assets/error-entries-D00nWhWE.js">
185
+ <link rel="modulepreload" crossorigin href="./assets/datasource-D1_5m7Yd.js">
186
+ <link rel="modulepreload" crossorigin href="./assets/error-entries-Dx8Rtti7.js">
187
187
  <link rel="modulepreload" crossorigin href="./assets/alert-DTaqCu_d.js">
188
- <link rel="modulepreload" crossorigin href="./assets/state-DZN9vZ6z.js">
189
- <link rel="modulepreload" crossorigin href="./assets/cell-link-D3rkqgbU.js">
190
- <link rel="modulepreload" crossorigin href="./assets/MarimoErrorOutput-Bzo6Bkp5.js">
188
+ <link rel="modulepreload" crossorigin href="./assets/state-DMO2unyy.js">
189
+ <link rel="modulepreload" crossorigin href="./assets/cell-link-I5T63SlT.js">
190
+ <link rel="modulepreload" crossorigin href="./assets/MarimoErrorOutput-BzRg-_e8.js">
191
191
  <link rel="modulepreload" crossorigin href="./assets/useDateFormatter-DA-62E-Y.js">
192
192
  <link rel="modulepreload" crossorigin href="./assets/en-US-Buu61juQ.js">
193
193
  <link rel="modulepreload" crossorigin href="./assets/isValid-q_Ad80ug.js">
@@ -207,13 +207,13 @@
207
207
  <link rel="modulepreload" crossorigin href="./assets/table-vrvqpuKJ.js">
208
208
  <link rel="modulepreload" crossorigin href="./assets/react-resizable-panels.browser.esm-Dbe0MrFr.js">
209
209
  <link rel="modulepreload" crossorigin href="./assets/useStateForKey-CGMJQT8x.js">
210
- <link rel="modulepreload" crossorigin href="./assets/JsonOutput-B7pGj79O.js">
210
+ <link rel="modulepreload" crossorigin href="./assets/JsonOutput-CKF7zS56.js">
211
211
  <link rel="modulepreload" crossorigin href="./assets/chart-no-axes-column-C4_v6fQW.js">
212
212
  <link rel="modulepreload" crossorigin href="./assets/square-function-_W_Lo0P4.js">
213
213
  <link rel="modulepreload" crossorigin href="./assets/spec-DlH4Mp7d.js">
214
214
  <link rel="modulepreload" crossorigin href="./assets/semaphore-BjoL5P0h.js">
215
215
  <link rel="modulepreload" crossorigin href="./assets/fileToBase64-BgBraYAr.js">
216
- <link rel="modulepreload" crossorigin href="./assets/useBoolean-Dd2nWXFJ.js">
216
+ <link rel="modulepreload" crossorigin href="./assets/useBoolean-rA1CzmYl.js">
217
217
  <link rel="modulepreload" crossorigin href="./assets/code-xml-SQaZqvcO.js">
218
218
  <link rel="modulepreload" crossorigin href="./assets/file-headphone-Dl35U6h3.js">
219
219
  <link rel="modulepreload" crossorigin href="./assets/file-BPBVl9pl.js">
@@ -221,11 +221,11 @@
221
221
  <link rel="modulepreload" crossorigin href="./assets/file-icons-CdJgUy-I.js">
222
222
  <link rel="modulepreload" crossorigin href="./assets/globe-UdPdIbF7.js">
223
223
  <link rel="modulepreload" crossorigin href="./assets/esm-CI_o7NhW.js">
224
- <link rel="modulepreload" crossorigin href="./assets/useAddCell-CaXnFGki.js">
225
- <link rel="modulepreload" crossorigin href="./assets/readonly-python-code-Cqkfdeyo.js">
224
+ <link rel="modulepreload" crossorigin href="./assets/useAddCell-bvRpB5dY.js">
225
+ <link rel="modulepreload" crossorigin href="./assets/readonly-python-code-BhBEAP6F.js">
226
226
  <link rel="modulepreload" crossorigin href="./assets/data-grid-types-DgHVN_Zz.js">
227
227
  <link rel="modulepreload" crossorigin href="./assets/glide-utils-HFqhvWHI.js">
228
- <link rel="modulepreload" crossorigin href="./assets/column-preview-yI7-INkZ.js">
228
+ <link rel="modulepreload" crossorigin href="./assets/column-preview-BdBJaVZ4.js">
229
229
  <link rel="modulepreload" crossorigin href="./assets/square-BPmgQFnh.js">
230
230
  <link rel="modulepreload" crossorigin href="./assets/triangle-alert-DmIqR8sY.js">
231
231
  <link rel="modulepreload" crossorigin href="./assets/prop-types-Djf4O40e.js">
@@ -238,7 +238,7 @@
238
238
  <link rel="modulepreload" crossorigin href="./assets/useDeepCompareMemoize-B2DVK2De.js">
239
239
  <link rel="modulepreload" crossorigin href="./assets/useDelayElapsed-BlsDT4TV.js">
240
240
  <link rel="modulepreload" crossorigin href="./assets/pathUtils-DPmlLd-r.js">
241
- <link rel="modulepreload" crossorigin href="./assets/floating-outline-B-k3P_AO.js">
241
+ <link rel="modulepreload" crossorigin href="./assets/floating-outline-PAiDriV4.js">
242
242
  <link rel="stylesheet" crossorigin href="./assets/cells-0PFKL3SH.css">
243
243
  <link rel="stylesheet" crossorigin href="./assets/markdown-renderer-DjIHoF9Z.css">
244
244
  <link rel="stylesheet" crossorigin href="./assets/JsonOutput-B0GmlMnR.css">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/frontend",
3
- "version": "0.25.3-dev125",
3
+ "version": "0.25.3-dev127",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -6,8 +6,9 @@ import { createStore } from "jotai";
6
6
  import { afterEach, describe, expect, it, vi } from "vitest";
7
7
  import { Deferred } from "@/utils/Deferred";
8
8
  import { ConnectionState } from "../../websocket/types";
9
+ import type { NotebookBackend } from "../backend";
9
10
  import type { ConnectionStatus } from "../connection";
10
- import { createNotebookClient, type NotebookClient } from "../notebook-client";
11
+ import { createNotebookClient } from "../notebook-client";
11
12
  import { fixtureEvents, fixtureMessages } from "./fixtures";
12
13
 
13
14
  /** One connection the fake transport opened. */
@@ -22,7 +23,14 @@ interface Attempt {
22
23
  stop(status?: ConnectionStatus): void;
23
24
  }
24
25
 
25
- function setup(edit?: NotebookClient["edit"]) {
26
+ type Commands = ReturnType<NotebookBackend["notebook"]>;
27
+ /** A backend edit with the notebook it was sent to. */
28
+ type Edit = (
29
+ id: string,
30
+ ...args: Parameters<Commands["edit"]>
31
+ ) => ReturnType<Commands["edit"]>;
32
+
33
+ function setup(edit = vi.fn<Edit>()) {
26
34
  const attempts: Attempt[] = [];
27
35
  const transport: NotebookTransport = ({ cursor, signal }, handlers) =>
28
36
  new Promise((resolve, reject) => {
@@ -40,11 +48,15 @@ function setup(edit?: NotebookClient["edit"]) {
40
48
  const onNotification = vi.fn();
41
49
  const checkHealth = vi.fn(async () => true);
42
50
  const store = createStore();
43
- const controller = createNotebookClient(store, { edit });
51
+ const backend: NotebookBackend = {
52
+ transport,
53
+ notebook: (id) => ({ edit: (...args) => edit(id, ...args) }),
54
+ };
55
+ const controller = createNotebookClient(store);
44
56
  const client = controller.client;
45
57
  const connect = (signal: AbortSignal) =>
46
58
  controller.connect({
47
- transport,
59
+ backend,
48
60
  signal,
49
61
  checkHealth,
50
62
  onNotification: (notification) => onNotification({ data: notification }),
@@ -99,7 +111,7 @@ const notebook = {
99
111
 
100
112
  describe("NotebookClient lifetime", () => {
101
113
  it("keeps an edit's target and request identity across a stream reset", async () => {
102
- const edit = vi.fn<NotebookClient["edit"]>().mockResolvedValue({
114
+ const edit = vi.fn<Edit>().mockResolvedValue({
103
115
  ok: false,
104
116
  status: 409,
105
117
  problem: { type: "conflict", title: "conflict", status: 409 },
@@ -127,7 +139,7 @@ describe("NotebookClient lifetime", () => {
127
139
  expect(edit).toHaveBeenCalledTimes(2);
128
140
  });
129
141
 
130
- it("rejects aborted edits and reports a missing edit adapter", async () => {
142
+ it("rejects aborted edits and reports edits before connecting", async () => {
131
143
  const { client } = setup();
132
144
  const controller = lifetime();
133
145
  await expect(
@@ -129,7 +129,7 @@ describe("static notebook backend", () => {
129
129
 
130
130
  it("advertises no mutations and returns a protocol failure for direct edits", async () => {
131
131
  const backend = createStaticNotebookBackend(snapshot());
132
- expect(await backend.client.edit([])).toEqual({
132
+ expect(await backend.notebook(snapshot().notebook.id).edit([])).toEqual({
133
133
  ok: false,
134
134
  status: 501,
135
135
  problem: {
@@ -154,7 +154,9 @@ describe("static notebook backend", () => {
154
154
  expect(open).not.toHaveBeenCalled();
155
155
  expect(message).not.toHaveBeenCalled();
156
156
  await expect(
157
- backend.client.edit([], { signal: before.signal }),
157
+ backend
158
+ .notebook(snapshot().notebook.id)
159
+ .edit([], { signal: before.signal }),
158
160
  ).rejects.toBe(before.signal.reason);
159
161
  const during = new AbortController();
160
162
  message.mockImplementation(() => during.abort());
@@ -102,9 +102,9 @@ describe("WASM notebook backend", () => {
102
102
  it("sends edits as calls with the retry key and validates replies", async () => {
103
103
  const { bridge, reply } = worker();
104
104
  const backend = createWasmNotebookBackend(bridge);
105
- expect(await backend.client.edit([], { idempotencyKey: "edit-7" })).toEqual(
106
- reply,
107
- );
105
+ expect(
106
+ await backend.notebook("n1").edit([], { idempotencyKey: "edit-7" }),
107
+ ).toEqual(reply);
108
108
  expect(bridge.call).toHaveBeenCalledWith({
109
109
  operation: "notebook.edit",
110
110
  params: { notebook_id: "n1" },
@@ -117,13 +117,15 @@ describe("WASM notebook backend", () => {
117
117
  status: 403,
118
118
  };
119
119
  bridge.call.mockResolvedValue({ status: 403, body: problem });
120
- expect(await backend.client.edit([])).toEqual({
120
+ expect(await backend.notebook("n1").edit([])).toEqual({
121
121
  ok: false,
122
122
  status: 403,
123
123
  problem,
124
124
  });
125
125
  bridge.call.mockResolvedValue({ status: 200, body: {} });
126
- await expect(backend.client.edit([])).rejects.toThrow(/Invalid input/);
126
+ await expect(backend.notebook("n1").edit([])).rejects.toThrow(
127
+ /Invalid input/,
128
+ );
127
129
  });
128
130
 
129
131
  it("cancels waiting for an accepted edit and permits retry with the same key", async () => {
@@ -132,7 +134,7 @@ describe("WASM notebook backend", () => {
132
134
  bridge.call.mockReturnValue(pending.promise);
133
135
  const backend = createWasmNotebookBackend(bridge);
134
136
  const controller = new AbortController();
135
- const editing = backend.client.edit([], {
137
+ const editing = backend.notebook("n1").edit([], {
136
138
  signal: controller.signal,
137
139
  idempotencyKey: "edit-8",
138
140
  });
@@ -140,12 +142,12 @@ describe("WASM notebook backend", () => {
140
142
  controller.abort();
141
143
  await expect(editing).rejects.toBe(controller.signal.reason);
142
144
  pending.resolve(wire);
143
- expect(await backend.client.edit([], { idempotencyKey: "edit-8" })).toEqual(
144
- reply,
145
- );
145
+ expect(
146
+ await backend.notebook("n1").edit([], { idempotencyKey: "edit-8" }),
147
+ ).toEqual(reply);
146
148
  expect(bridge.call.mock.calls[0]).toEqual(bridge.call.mock.calls[1]);
147
149
  await expect(
148
- backend.client.edit([], { signal: controller.signal }),
150
+ backend.notebook("n1").edit([], { signal: controller.signal }),
149
151
  ).rejects.toBe(controller.signal.reason);
150
152
  expect(bridge.call).toHaveBeenCalledTimes(2);
151
153
  });
@@ -1,10 +1,15 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import type { NotebookClient, NotebookTransport } from "@marimo-team/client";
4
+ import type { NotebookId } from "@marimo-team/client/protocol";
4
5
 
5
- /** Events and commands for the same notebook, independent of React or a replica. */
6
+ /** Events and commands for a notebook, independent of React or a replica. */
6
7
  export interface NotebookBackend {
7
8
  readonly transport: NotebookTransport;
8
- /** Method presence is not permission; the notebook advertises its operations. */
9
- readonly client: Pick<NotebookClient, "edit">;
9
+ /**
10
+ * Commands for a notebook the transport reported.
11
+ *
12
+ * Method presence is not permission. The notebook advertises its operations.
13
+ */
14
+ notebook(id: NotebookId): Pick<NotebookClient, "edit">;
10
15
  }
@@ -5,7 +5,6 @@ import {
5
5
  createNotebookReducer,
6
6
  type NotebookEvent,
7
7
  NotebookEvents,
8
- type NotebookTransport,
9
8
  type Result,
10
9
  StreamStopped,
11
10
  } from "@marimo-team/client";
@@ -18,6 +17,7 @@ import type {
18
17
  } from "@marimo-team/client/protocol";
19
18
  import { type Atom, atom, type createStore } from "jotai/vanilla";
20
19
  import { z } from "zod";
20
+ import type { NotebookBackend } from "./backend";
21
21
  import {
22
22
  ConnectionClosedReason,
23
23
  ConnectionState,
@@ -60,10 +60,10 @@ export interface NotebookClient {
60
60
  reconnect(): Promise<void>;
61
61
  }
62
62
 
63
- /** The transport and application callbacks needed to follow a notebook. */
63
+ /** The backend and application callbacks needed to follow a notebook. */
64
64
  export interface NotebookConnection {
65
- /** Opens one connection to the notebook's stream. */
66
- transport: NotebookTransport;
65
+ /** Follows the notebook's stream and sends its commands. */
66
+ backend: NotebookBackend;
67
67
  /** Abort to close the stream and clear the client's state. */
68
68
  signal: AbortSignal;
69
69
  /** Check whether the host is reachable before an explicit reconnect. */
@@ -103,14 +103,8 @@ function statusAfter(error: unknown): ConnectionStatus {
103
103
  return disconnected;
104
104
  }
105
105
 
106
- /**
107
- * Create a client without opening a connection.
108
- * The session supplies edits authenticated for the same host as its stream.
109
- */
110
- export function createNotebookClient(
111
- store: ReturnType<typeof createStore>,
112
- { edit }: { edit?: NotebookClient["edit"] } = {},
113
- ) {
106
+ /** Create a client without opening a connection. */
107
+ export function createNotebookClient(store: ReturnType<typeof createStore>) {
114
108
  // `useNotebookSession` copies this into the editor's shared `connectionAtom`,
115
109
  // which gates features until MO-8051 derives availability separately.
116
110
  const connectionAtom = atom<ConnectionStatus>({
@@ -137,6 +131,7 @@ export function createNotebookClient(
137
131
  : attachments.initial,
138
132
  );
139
133
  let reconnect: (() => Promise<void>) | undefined;
134
+ let backend: NotebookBackend | undefined;
140
135
  const client: NotebookClient = {
141
136
  connectionAtom,
142
137
  notebookAtom,
@@ -147,7 +142,7 @@ export function createNotebookClient(
147
142
  if (currentId !== null && currentId !== notebookId) {
148
143
  throw new Error("The notebook changed before the edit was sent");
149
144
  }
150
- if (edit === undefined) {
145
+ if (backend === undefined) {
151
146
  return {
152
147
  ok: false,
153
148
  status: 501,
@@ -158,7 +153,7 @@ export function createNotebookClient(
158
153
  },
159
154
  };
160
155
  }
161
- return edit(notebookId, changes, options);
156
+ return backend.notebook(notebookId).edit(changes, options);
162
157
  },
163
158
  attachmentsAtom,
164
159
  reconnect: async () => reconnect?.(),
@@ -170,7 +165,7 @@ export function createNotebookClient(
170
165
  }
171
166
 
172
167
  function connect({
173
- transport,
168
+ backend: connected,
174
169
  signal,
175
170
  checkHealth,
176
171
  onNotification,
@@ -178,6 +173,8 @@ export function createNotebookClient(
178
173
  if (signal.aborted) {
179
174
  return;
180
175
  }
176
+ backend = connected;
177
+ const { transport } = connected;
181
178
  let events: NotebookEvents<(typeof TYPES)[number]> | undefined;
182
179
  let checkingHealth = false;
183
180
  // Whether the replica still holds the state the last cursor describes.
@@ -283,6 +280,9 @@ export function createNotebookClient(
283
280
  "abort",
284
281
  () => {
285
282
  reconnect = undefined;
283
+ if (backend === connected) {
284
+ backend = undefined;
285
+ }
286
286
  store.set(streamAtom, reducer.initial);
287
287
  setStatus({ state: ConnectionState.NOT_STARTED });
288
288
  },
@@ -51,7 +51,7 @@ export function createStaticNotebookBackend(
51
51
  message(frame);
52
52
  }
53
53
  }),
54
- client: {
54
+ notebook: () => ({
55
55
  edit: async (_changes, options) => {
56
56
  options?.signal?.throwIfAborted();
57
57
  return {
@@ -65,6 +65,6 @@ export function createStaticNotebookBackend(
65
65
  },
66
66
  };
67
67
  },
68
- },
68
+ }),
69
69
  };
70
70
  }
@@ -39,6 +39,9 @@ function abortable<T>(
39
39
  export function createWasmNotebookBackend(
40
40
  bridge: WasmNotebookBridge,
41
41
  ): NotebookBackend {
42
+ const client = NotebookClient.unstable_over((call) => bridge.call(call), {
43
+ notebookId: bridge.notebookId,
44
+ });
42
45
  return {
43
46
  transport: async ({ cursor, signal }, { open, message }) => {
44
47
  if (signal.aborted) {
@@ -85,8 +88,6 @@ export function createWasmNotebookBackend(
85
88
  signal.removeEventListener("abort", abort);
86
89
  }
87
90
  },
88
- client: NotebookClient.unstable_over((call) => bridge.call(call), {
89
- notebookId: bridge.notebookId,
90
- }),
91
+ notebook: () => client,
91
92
  };
92
93
  }
@@ -220,6 +220,23 @@ describe("RuntimeManager", () => {
220
220
  });
221
221
  });
222
222
 
223
+ describe("getNotebookURL", () => {
224
+ it("addresses the notebook below the host API without the auth token", () => {
225
+ const runtime = new RuntimeManager(
226
+ {
227
+ url: "https://sandbox.example.com/base",
228
+ lazy: true,
229
+ authToken: "my-secret-token",
230
+ },
231
+ true,
232
+ );
233
+ const url = runtime.getNotebookURL("n/1");
234
+
235
+ expect(url.pathname).toBe("/base/api/marimo/v1/notebooks/n%2F1");
236
+ expect(url.toString()).not.toContain("my-secret-token");
237
+ });
238
+ });
239
+
223
240
  describe("getSseURL", () => {
224
241
  it("should never put the auth token in the URL, even cross-origin", () => {
225
242
  // SSE requests can send headers, so auth travels in the
@@ -1,5 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
+ import type { NotebookId } from "@marimo-team/client/protocol";
3
4
  import { Deferred } from "@/utils/Deferred";
4
5
  import { Logger } from "@/utils/Logger";
5
6
  import { KnownQueryParams } from "../constants";
@@ -171,6 +172,18 @@ export class RuntimeManager {
171
172
  return url;
172
173
  }
173
174
 
175
+ /**
176
+ * The host API URL of a notebook, below which its operations are served.
177
+ * Requests send auth in headers (see `headers()`), never in the URL.
178
+ */
179
+ public getNotebookURL(notebookId: NotebookId): URL {
180
+ const url = this.formatHttpURL({
181
+ path: `/api/marimo/v1/notebooks/${encodeURIComponent(notebookId)}`,
182
+ });
183
+ url.searchParams.delete(KnownQueryParams.accessToken);
184
+ return url;
185
+ }
186
+
174
187
  /**
175
188
  * The WebSocket Sync URL of the runtime, for real-time updates.
176
189
  */
@@ -0,0 +1,111 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { NotebookEvents } from "@marimo-team/client";
4
+ import { afterEach, describe, expect, it, vi } from "vitest";
5
+ import { createEditorBackend } from "../backend";
6
+
7
+ const wasm = vi.hoisted(() => ({ enabled: false, consumer: vi.fn() }));
8
+ vi.mock("../../wasm/utils", () => ({ isWasm: () => wasm.enabled }));
9
+ vi.mock("../../wasm/bridge", () => ({
10
+ PyodideBridge: { INSTANCE: { attachMessageConsumer: wasm.consumer } },
11
+ }));
12
+
13
+ afterEach(() => {
14
+ wasm.enabled = false;
15
+ wasm.consumer.mockReset();
16
+ vi.unstubAllGlobals();
17
+ });
18
+
19
+ describe("createEditorBackend", () => {
20
+ const options = {
21
+ static: false,
22
+ transportType: "websocket" as const,
23
+ url: () => "ws://localhost/ws",
24
+ headers: () => ({}),
25
+ notebookUrl: (id: string) =>
26
+ new URL(`http://localhost/api/marimo/v1/notebooks/${id}`),
27
+ };
28
+
29
+ it.each([
30
+ ["a static notebook", { ...options, static: true }, "idle"],
31
+ ["WASM", options, "wasm"],
32
+ ["SSE", { ...options, transportType: "sse" as const }, "fetch"],
33
+ ["the default", options, "socket"],
34
+ ] as const)("follows %s", async (_name, chosen, expected) => {
35
+ wasm.enabled = expected === "wasm";
36
+ const fetch = vi.fn(() => new Promise<Response>(() => undefined));
37
+ const socket = vi.fn();
38
+ vi.stubGlobal("fetch", fetch);
39
+ vi.stubGlobal(
40
+ "WebSocket",
41
+ class extends EventTarget {
42
+ public constructor(url: URL) {
43
+ super();
44
+ socket(url);
45
+ }
46
+ public close() {}
47
+ },
48
+ );
49
+ const events = new NotebookEvents(createEditorBackend(chosen).transport);
50
+ await vi.waitFor(() =>
51
+ expect(
52
+ {
53
+ idle:
54
+ events.status === "connected" && !wasm.consumer.mock.calls.length,
55
+ wasm: wasm.consumer.mock.calls.length === 1,
56
+ fetch: fetch.mock.calls.length === 1,
57
+ socket: socket.mock.calls.length === 1,
58
+ }[expected],
59
+ ).toBe(true),
60
+ );
61
+ events.close();
62
+ });
63
+
64
+ it("edits a reported notebook on its host with the session's headers", async () => {
65
+ const fetch = vi.fn(
66
+ async (_input: RequestInfo | URL, _init?: RequestInit) =>
67
+ new Response(
68
+ JSON.stringify({ type: "conflict", title: "Conflict", status: 409 }),
69
+ {
70
+ status: 409,
71
+ headers: { "Content-Type": "application/problem+json" },
72
+ },
73
+ ),
74
+ );
75
+ vi.stubGlobal("fetch", fetch);
76
+ const backend = createEditorBackend({
77
+ ...options,
78
+ headers: () => ({
79
+ Authorization: "Bearer editor-token",
80
+ "Marimo-Server-Token": "server-token",
81
+ }),
82
+ });
83
+ const result = await backend
84
+ .notebook("n1")
85
+ .edit([], { idempotencyKey: "edit-1" });
86
+ expect(result).toMatchObject({ ok: false, status: 409 });
87
+ const [[input, init]] = fetch.mock.calls;
88
+ expect(String(input)).toBe(
89
+ "http://localhost/api/marimo/v1/notebooks/n1/edits",
90
+ );
91
+ const headers = new Headers(init?.headers);
92
+ expect(init?.method).toBe("POST");
93
+ expect(headers.get("Authorization")).toBe("Bearer editor-token");
94
+ expect(headers.get("Marimo-Server-Token")).toBe("server-token");
95
+ expect(headers.get("Idempotency-Key")).toBe("edit-1");
96
+ });
97
+
98
+ it.each([
99
+ ["a static notebook", { ...options, static: true }, false],
100
+ ["WASM", options, true],
101
+ ] as const)("does not edit %s", async (_name, chosen, isWasm) => {
102
+ wasm.enabled = isWasm;
103
+ const fetch = vi.fn();
104
+ vi.stubGlobal("fetch", fetch);
105
+ const result = await createEditorBackend(chosen)
106
+ .notebook("n1")
107
+ .edit([], { idempotencyKey: "edit-1" });
108
+ expect(result).toMatchObject({ ok: false, status: 501 });
109
+ expect(fetch).not.toHaveBeenCalled();
110
+ });
111
+ });