@marimo-team/frontend 0.25.3-dev4 → 0.25.3-dev40
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.
- package/dist/assets/CellStatus-BGIWF94J.js +1 -0
- package/dist/assets/{ConnectedDataExplorerComponent-t8fH_TNc.js → ConnectedDataExplorerComponent-D9UK8o9A.js} +1 -1
- package/dist/assets/{ErrorBoundary-Gl5nCA8C.js → ErrorBoundary-hliG40Pk.js} +2 -2
- package/dist/assets/JsonOutput-9tmC-p6h.js +11 -0
- package/dist/assets/{LazyAnyLanguageCodeMirror-CKcCbG2G.js → LazyAnyLanguageCodeMirror-BPYE76jR.js} +2 -2
- package/dist/assets/MarimoErrorOutput-BaHdbtck.js +10 -0
- package/dist/assets/{RenderHTML-BbOqmHy3.js → RenderHTML-B1FzB5NY.js} +1 -1
- package/dist/assets/{RunButton-CK3LhnP5.js → RunButton-DpGokdF1.js} +1 -1
- package/dist/assets/{add-cell-with-ai-8UBC0xwR.js → add-cell-with-ai-BYWG0iKO.js} +13 -13
- package/dist/assets/{add-connection-dialog-DXQnd6eB.js → add-connection-dialog-BWbUVPJk.js} +2 -2
- package/dist/assets/{agent-panel-DdkVL-Kx.js → agent-panel-DKNiQrAl.js} +5 -5
- package/dist/assets/ai-model-dropdown-CIg9eu47.js +13 -0
- package/dist/assets/{alert-dialog-BaPGzeOK.js → alert-dialog-C-sWmKva.js} +1 -1
- package/dist/assets/any-language-editor-DUMe1UCk.js +1 -0
- package/dist/assets/{app-config-button-BKEG4x9u.js → app-config-button-BvkL2eSY.js} +1 -1
- package/dist/assets/arrays-0R2reF2y.js +1 -0
- package/dist/assets/{blob-D5nfNHOo.js → blob-BQfLiwKr.js} +1 -1
- package/dist/assets/{button-COI-NGrx.js → button-D8gVKwe-.js} +1 -1
- package/dist/assets/{cache-panel-DTY1dqvi.js → cache-panel-Byolv2ht.js} +1 -1
- package/dist/assets/{capabilities-BJvf-MD4.js → capabilities-Pq1YNPlK.js} +1 -1
- package/dist/assets/{cell-editor-32lVhib7.js → cell-editor-BKKUv4d5.js} +9 -9
- package/dist/assets/{cell-link-LSth_8hT.js → cell-link-B_QkRkm-.js} +1 -1
- package/dist/assets/{cells-BRkCaSsh.js → cells-CdH98VSn.js} +31 -31
- package/dist/assets/{chat-display-Dnagy4Nv.js → chat-display-CNbRSebo.js} +1 -1
- package/dist/assets/{chat-panel-DN9vsbgS.js → chat-panel-HbEKDf0c.js} +2 -2
- package/dist/assets/{chat-ui-Df8Vcj6n.js → chat-ui-BwMev8Bf.js} +1 -1
- package/dist/assets/{circle-DXf-VWZG.js → circle-B6D_6D8w.js} +1 -1
- package/dist/assets/code-visibility-sQ37CBNo.js +1 -0
- package/dist/assets/coerce-DWjFeXOY.js +1 -0
- package/dist/assets/{column-preview-CHK_DNrs.js → column-preview-CsEBzIUE.js} +1 -1
- package/dist/assets/command-palette-BzZ86pUz.js +1 -0
- package/dist/assets/{common-Cu6CDbuw.js → common-S2v8dsEy.js} +1 -1
- package/dist/assets/{components-CGw16R_N.js → components-D-TnFWjo.js} +1 -1
- package/dist/assets/{components-CP_UoFtT.js → components-DrU8dOzy.js} +1 -1
- package/dist/assets/{components-CXfO-F47.js → components-lDm0qi2t.js} +1 -1
- package/dist/assets/config-BoBwH7CU.js +1 -0
- package/dist/assets/config-CYsbZpwa.js +104 -0
- package/dist/assets/config-DGX7JYor.js +1 -0
- package/dist/assets/{copy-D8O_CYCG.js → copy-KlhazlhB.js} +1 -1
- package/dist/assets/{copy-icon-BIiRXh_3.js → copy-icon-8-a5vovc.js} +1 -1
- package/dist/assets/{createReducer-DgRODTW6.js → createReducer-HKXUbqyz.js} +1 -1
- package/dist/assets/{datasource-Cq6x_5ML.js → datasource-BA7YF4eo.js} +1 -1
- package/dist/assets/{dates-CfmdTL6B.js → dates-CQMwFko2.js} +1 -1
- package/dist/assets/{dependency-graph-panel--Kgtktcw.js → dependency-graph-panel-DsjI61Qa.js} +1 -1
- package/dist/assets/{dist-iaThVDkA.js → dist-jlhMTKo_.js} +1 -1
- package/dist/assets/{documentation-panel-BHmbeznw.js → documentation-panel-hk3IdeXy.js} +1 -1
- package/dist/assets/{download-DCJXy66H.js → download-CCZkq0y4.js} +1 -1
- package/dist/assets/{dropdown-menu-C1rnl-0o.js → dropdown-menu-DS6umAnH.js} +1 -1
- package/dist/assets/edit-page-BgS6tjth.js +9 -0
- package/dist/assets/{error-banner-Cd69vvyX.js → error-banner-B5kzVf_0.js} +1 -1
- package/dist/assets/{error-entries-Dk3VZ-OK.js → error-entries-BVXrGOTZ.js} +1 -1
- package/dist/assets/{error-panel-BPRYmC5J.js → error-panel-DYethUUR.js} +1 -1
- package/dist/assets/{errors-DCrsa1nH.js → errors-CNsgAw6m.js} +1 -1
- package/dist/assets/{es-Cqst59WX.js → es-N9SHpmG7.js} +1 -1
- package/dist/assets/feedback-button-DDzObtF2.js +1 -0
- package/dist/assets/{feedback-button-Bik2-e33.js → feedback-button-DaNc3uJa.js} +2 -2
- package/dist/assets/{file-explorer-panel-BmekFu7T.js → file-explorer-panel-CqnzSSYc.js} +1 -1
- package/dist/assets/{file-name-input-Cdc-TVqO.js → file-name-input-D4AvwZV9.js} +1 -1
- package/dist/assets/{floating-outline-XD9FAV-f.js → floating-outline-CTy-Iakk.js} +1 -1
- package/dist/assets/{formats-yZ06h1Pl.js → formats-zylfwejq.js} +1 -1
- package/dist/assets/{formatting-BqrP-n49.js → formatting-uom1es4b.js} +1 -1
- package/dist/assets/{fullscreen-B9tHcfpV.js → fullscreen-BWTs8pYZ.js} +1 -1
- package/dist/assets/{gallery-page-Dm6mGwBi.js → gallery-page-ByjlvM6k.js} +1 -1
- package/dist/assets/{ganttDiagram-EL5Y4UJY-2n7Ig5Jm.js → ganttDiagram-EL5Y4UJY-CYqkCfFG.js} +1 -1
- package/dist/assets/{glide-data-editor-J9CAb70d.js → glide-data-editor-CABBj2b0.js} +2 -2
- package/dist/assets/{glide-utils-CIrAxdjv.js → glide-utils-HFqhvWHI.js} +2 -2
- package/dist/assets/{home-page-s3LUizSn.js → home-page-DejRSjeJ.js} +3 -3
- package/dist/assets/hooks-BJgCcVXZ.js +1 -0
- package/dist/assets/{html-to-image-DK9Zl1lU.js → html-to-image-D_0rd3_B.js} +1 -1
- package/dist/assets/index-ChfDZJzP.css +2 -0
- package/dist/assets/index-PDJQd7X6.js +30 -0
- package/dist/assets/{input-BoBxR50a.js → input-ByHt6aoV.js} +1 -1
- package/dist/assets/{kiosk-mode-BZECfg9k.js → kiosk-mode-BGmMSc7A.js} +1 -1
- package/dist/assets/{layout-B_GMOySr.js → layout-Ct0rjb-5.js} +5 -5
- package/dist/assets/linear-Bma8J3iM.js +1 -0
- package/dist/assets/links-CXcpNIM0.js +1 -0
- package/dist/assets/{logs-panel-ABM16htT.js → logs-panel-TyeqEvOY.js} +1 -1
- package/dist/assets/{markdown-renderer-BG3sp2rs.js → markdown-renderer-BFyD6Jhs.js} +1 -1
- package/dist/assets/{menu-items-BrwnTgyK.js → menu-items-BgUyNUpu.js} +1 -1
- package/dist/assets/{mermaid-a3xYymTy.js → mermaid-B6mqMm5_.js} +2 -2
- package/dist/assets/{name-cell-input-BgEBmqyL.js → name-cell-input-D-0VF3qd.js} +1 -1
- package/dist/assets/numbers-CsZVw3Dc.js +1 -0
- package/dist/assets/{objects-snlMn4yk.js → objects-rLPlQ3X_.js} +1 -1
- package/dist/assets/{once-Haz_uxEL.js → once-6KNTRW0u.js} +1 -1
- package/dist/assets/{outline-panel-DCXQ3_F6.js → outline-panel-DMTjk2WC.js} +1 -1
- package/dist/assets/{packages-panel-BMGOmZkR.js → packages-panel-DrT69k9L.js} +1 -1
- package/dist/assets/{panel-accordion-state-C1sdPp0m.js → panel-accordion-state-Ban6j---.js} +1 -1
- package/dist/assets/panels-BLYQRkEc.js +4 -0
- package/dist/assets/precisionRound-BxlUs8G4.js +1 -0
- package/dist/assets/process-output-DzpQYlpY.js +1 -0
- package/dist/assets/{quadrantDiagram-AXDQQJYC-yBhCDI7E.js → quadrantDiagram-AXDQQJYC-D9KH5Zge.js} +1 -1
- package/dist/assets/react-vega-BTNeboR2.js +189 -0
- package/dist/assets/{readonly-code-display-Do50PHI8.js → readonly-code-display-Cyn3k5TX.js} +1 -1
- package/dist/assets/{readonly-python-code-DCSMIsTX.js → readonly-python-code-DrXDDAnA.js} +1 -1
- package/dist/assets/{renderShortcut-BkVJQ_Zh.js → renderShortcut-H60uiHBp.js} +1 -1
- package/dist/assets/{reveal-component-CLE6vB1Z.js → reveal-component-_lKImPWP.js} +3 -3
- package/dist/assets/{run-page-Depd6lEE.js → run-page-7UNg--Rj.js} +1 -1
- package/dist/assets/{runs-BLp5jezv.js → runs-6lJxKV1w.js} +1 -1
- package/dist/assets/{runtime-details-state-CSPobEdR.js → runtime-details-state-CKOKhMhv.js} +1 -1
- package/dist/assets/{schemas-Dv42wLFq.js → schemas-ChRqMOTL.js} +1 -1
- package/dist/assets/{scratchpad-panel-lc6jmpjv.js → scratchpad-panel-C7h4MgqN.js} +1 -1
- package/dist/assets/{scroll-to-bottom-button-1FyowpWV.js → scroll-to-bottom-button-CIkON2WJ.js} +1 -1
- package/dist/assets/{secrets-panel-Bctbtkrp.js → secrets-panel-CmLZsNfp.js} +1 -1
- package/dist/assets/{select-DTBn5eTb.js → select-3CVGlYrC.js} +1 -1
- package/dist/assets/{session-DqoWmb7X.js → session-B8Y-GJnS.js} +1 -1
- package/dist/assets/session-panel-CLiWXc5y.js +1 -0
- package/dist/assets/{snippets-panel-Bh1oTw4m.js → snippets-panel-Wrqaorxa.js} +1 -1
- package/dist/assets/{spec-BfSBcO5B.js → spec-uCpVvaAR.js} +1 -1
- package/dist/assets/{state-uwRvJLb8.js → state-BY4_P6ko.js} +1 -1
- package/dist/assets/{state-BAJYRhmO.js → state-C0irhlOZ.js} +1 -1
- package/dist/assets/{state-DxQnC_1I.js → state-Ck7FMD5A.js} +1 -1
- package/dist/assets/{state-C3oDoyFe.js → state-DaWgt-rn.js} +1 -1
- package/dist/assets/state-TVREh4u4.js +3 -0
- package/dist/assets/{state-XsPO8hNi.js → state-_uXVmwuN.js} +1 -1
- package/dist/assets/{strings-B2vv-ENv.js → strings-C25fhMR_.js} +1 -1
- package/dist/assets/{swiper-component-a07domrZ.js → swiper-component-CRGRy5_O.js} +1 -1
- package/dist/assets/{switch-CTaiAjrz.js → switch-1h011wlH.js} +1 -1
- package/dist/assets/{tabs-BuQlZFbt.js → tabs-CY8E4r9U.js} +1 -1
- package/dist/assets/{terminal-Ck_vK-34.js → terminal-Dp5gyJ7l.js} +3 -3
- package/dist/assets/{time-MgToa8VM.js → time-DswSXBDk.js} +1 -1
- package/dist/assets/{toast-BGzLGnvw.js → toast-Gzxi-le_.js} +1 -1
- package/dist/assets/tooltip-Brya4Ghn.js +61 -0
- package/dist/assets/{tooltip-C9TuY18o.js → tooltip-CkGm8X7m.js} +1 -1
- package/dist/assets/tracing-fUNLyKuj.js +1 -0
- package/dist/assets/{tracing-panel-PeRXHD8_.js → tracing-panel-Cx4OqOoP.js} +2 -2
- package/dist/assets/{tree-actions-GM5E_bUs.js → tree-actions-B1vsdenY.js} +1 -1
- package/dist/assets/{use-restore-focus-BxEGwIbk.js → use-restore-focus-DdS7Tl0F.js} +1 -1
- package/dist/assets/{useAddCell-GR7eEQrT.js → useAddCell-DZ-50qEG.js} +1 -1
- package/dist/assets/useAsyncData-DhhdOGGd.js +1 -0
- package/dist/assets/{useBoolean-BDD6_xQQ.js → useBoolean-5g-bBaYF.js} +1 -1
- package/dist/assets/{useCellActionButton-B-9eG5z_.js → useCellActionButton-DINnRstv.js} +1 -1
- package/dist/assets/{useDeleteCell-2_bMClHd.js → useDeleteCell-CREJXVNp.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-k3C3M18S.js → useDependencyPanelTab-Cw5VT16Q.js} +1 -1
- package/dist/assets/{useEventListener-D9cftcHj.js → useEventListener-CjDeLqFC.js} +1 -1
- package/dist/assets/{useIframeCapabilities-CwTkczVN.js → useIframeCapabilities-Czr9e4uG.js} +1 -1
- package/dist/assets/{useInstallPackage-iX1eufdl.js → useInstallPackage-Cw0dVELh.js} +1 -1
- package/dist/assets/{useInterval--x3s1VpI.js → useInterval-DJdQmXnq.js} +1 -1
- package/dist/assets/useNotebookActions-B4av4mLc.js +1 -0
- package/dist/assets/{useRunCells-B2vUhREg.js → useRunCells-BHBMk9zW.js} +1 -1
- package/dist/assets/{useSplitCell-DEb03htp.js → useSplitCell-DAgwZV0q.js} +1 -1
- package/dist/assets/{useTheme-COhSHkCj.js → useTheme-DLtPc-39.js} +1 -1
- package/dist/assets/{utils-CaQYvazl.js → utils-DfElfBpz.js} +1 -1
- package/dist/assets/vega-component-pQE2mKc1.js +1 -0
- package/dist/assets/{vega-embed-container-DJPNkHPb.js → vega-embed-container-DtArSJz1.js} +1 -1
- package/dist/assets/vega-loader.browser-Bu25TKax.js +6 -0
- package/dist/assets/{write-secret-modal-DzKAInL2.js → write-secret-modal-usRSi1bb.js} +1 -1
- package/dist/assets/{xychartDiagram-S5SC5T6Z-KbGzG6-u.js → xychartDiagram-S5SC5T6Z-CeRnBS5e.js} +1 -1
- package/dist/index.html +76 -77
- package/package.json +4 -2
- package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
- package/src/__tests__/mount.test.ts +28 -8
- package/src/components/app-config/ai-config.tsx +5 -1
- package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
- package/src/components/data-table/__tests__/export-actions.test.tsx +329 -6
- package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
- package/src/components/data-table/columns.tsx +2 -1
- package/src/components/data-table/date-popover.tsx +6 -4
- package/src/components/data-table/export-actions.tsx +192 -116
- package/src/components/data-table/row-viewer-panel/row-viewer.tsx +2 -2
- package/src/components/data-table/schemas.ts +14 -36
- package/src/components/editor/Output.tsx +9 -2
- package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
- package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +80 -252
- package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +186 -34
- package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
- package/src/components/editor/actions/pair-with-agent-modal.tsx +96 -36
- package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
- package/src/components/editor/chrome/panels/error-panel.tsx +1 -0
- package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +366 -0
- package/src/components/editor/chrome/wrapper/footer-item.tsx +32 -24
- package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +237 -0
- package/src/components/editor/chrome/wrapper/footer.tsx +2 -0
- package/src/components/editor/connections/components.tsx +1 -1
- package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
- package/src/components/editor/controls/command-palette.tsx +22 -10
- package/src/components/editor/dynamic-favicon.tsx +21 -40
- package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +537 -0
- package/src/components/editor/errors/send-error-report-button.tsx +69 -0
- package/src/components/editor/output/MarimoErrorOutput.tsx +21 -5
- package/src/components/editor/output/MarimoTracebackOutput.tsx +36 -21
- package/src/components/editor/output/__tests__/ansi-reduce.test.ts +18 -0
- package/src/components/editor/output/ansi-reduce.ts +6 -0
- package/src/components/pages/home-page.tsx +4 -4
- package/src/components/tracing/tracing.tsx +2 -2
- package/src/core/attachments/__tests__/toast.test.tsx +58 -0
- package/src/core/attachments/state.ts +18 -0
- package/src/core/attachments/toast.ts +15 -0
- package/src/core/cells/__tests__/cells.test.ts +52 -0
- package/src/core/cells/__tests__/error-report.test.ts +139 -0
- package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
- package/src/core/cells/__tests__/session.test.ts +50 -1
- package/src/core/cells/cells.ts +2 -1
- package/src/core/cells/error-report.ts +63 -0
- package/src/core/cells/pending-delete-service.ts +2 -6
- package/src/core/cells/session.ts +28 -1
- package/src/core/config/pair.ts +5 -3
- package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
- package/src/core/datasets/data-source-discovery.ts +4 -6
- package/src/core/edit-app.tsx +2 -2
- package/src/core/host/AGENTS.md +5 -0
- package/src/core/host/__tests__/attachments.test.ts +57 -0
- package/src/core/host/__tests__/document.test.ts +124 -0
- package/src/core/host/__tests__/events.test.ts +46 -0
- package/src/core/host/__tests__/fixtures.ts +39 -0
- package/src/core/host/__tests__/notebook-client.test.ts +433 -0
- package/src/core/host/__tests__/protocol.test.ts +15 -0
- package/src/core/host/__tests__/stream.test.ts +209 -0
- package/src/core/host/connection.ts +81 -0
- package/src/core/host/events.ts +61 -0
- package/src/core/host/notebook-client.ts +290 -0
- package/src/core/host/protocol.ts +11 -6
- package/src/core/host/resources/attachments.ts +19 -0
- package/src/core/host/resources/document.ts +101 -0
- package/src/core/host/resources/resource.ts +47 -0
- package/src/core/host/stream.ts +211 -0
- package/src/core/hotkeys/actions.ts +2 -4
- package/src/core/network/connection.ts +4 -2
- package/src/core/run-app.tsx +2 -2
- package/src/core/runtime/__tests__/runtime.test.ts +55 -11
- package/src/core/runtime/runtime.ts +18 -5
- package/src/core/runtime/types.ts +2 -0
- package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -17
- package/src/core/websocket/__tests__/{useWebSocket.test.ts → createConnectionTransport.test.ts} +1 -1
- package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +170 -0
- package/src/core/websocket/__tests__/{useMarimoKernelConnection.hook.test.tsx → useNotebookSession.hook.test.tsx} +181 -65
- package/src/core/websocket/connection-state.ts +70 -0
- package/src/core/websocket/state.ts +7 -0
- package/src/core/websocket/transports/__tests__/sse.test.ts +1 -4
- package/src/core/websocket/transports/create.ts +31 -0
- package/src/core/websocket/transports/transport.ts +12 -23
- package/src/core/websocket/types.ts +9 -39
- package/src/core/websocket/useNotebookClient.ts +88 -0
- package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +59 -225
- package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
- package/src/hooks/useAsyncData.ts +3 -3
- package/src/hooks/useDataSourceDiscovery.ts +6 -4
- package/src/hooks/useElapsedTime.ts +3 -3
- package/src/mount.tsx +7 -3
- package/src/plugins/core/registerReactComponent.tsx +1 -1
- package/src/plugins/impl/DropdownPlugin.tsx +2 -6
- package/src/plugins/impl/FormPlugin.tsx +1 -1
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +84 -0
- package/src/plugins/impl/code/any-language-editor.tsx +2 -2
- package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
- package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
- package/src/plugins/impl/vega/make-selectable.ts +3 -6
- package/src/utils/__tests__/arrays.test.ts +35 -0
- package/src/utils/__tests__/numbers.test.ts +48 -1
- package/src/utils/__tests__/objects.test.ts +21 -0
- package/src/utils/arrays.ts +10 -0
- package/src/utils/numbers.ts +28 -17
- package/src/utils/objects.ts +3 -7
- package/src/utils/once.ts +19 -19
- package/dist/assets/CellStatus-BP2mavAq.js +0 -1
- package/dist/assets/JsonOutput-DxHKOpPG.js +0 -11
- package/dist/assets/MarimoErrorOutput-D60XkCny.js +0 -6
- package/dist/assets/ai-model-dropdown-B0z6-zhp.js +0 -5
- package/dist/assets/any-language-editor-BLCAD3yx.js +0 -1
- package/dist/assets/api-BeC_VSGv.js +0 -1
- package/dist/assets/arrays-BqUx5USx.js +0 -1
- package/dist/assets/code-visibility-B-_GvwR5.js +0 -1
- package/dist/assets/coerce-CpE6kUdX.js +0 -1
- package/dist/assets/command-palette-Cf7WX-nr.js +0 -1
- package/dist/assets/config-BOKzA3tF.js +0 -1
- package/dist/assets/config-DX8Y2WbJ.js +0 -1
- package/dist/assets/config-VPSq73n8.js +0 -1
- package/dist/assets/edit-page-BT74CF4G.js +0 -9
- package/dist/assets/feedback-button-B2gKC5cn.js +0 -1
- package/dist/assets/hooks-RnjuFmEx.js +0 -1
- package/dist/assets/index-Bcdzex-3.css +0 -2
- package/dist/assets/index-D-kY3FU1.js +0 -29
- package/dist/assets/linear-0eij_aAG.js +0 -1
- package/dist/assets/links-DkkL9DVa.js +0 -1
- package/dist/assets/numbers-Mk3-CcoU.js +0 -1
- package/dist/assets/pair-CaQyK6En.js +0 -1
- package/dist/assets/panels-Dn59o8ay.js +0 -4
- package/dist/assets/precisionRound-Bq_nmf5u.js +0 -1
- package/dist/assets/process-output-D0KGq04R.js +0 -1
- package/dist/assets/react-vega-C3rYGtP6.js +0 -189
- package/dist/assets/session-panel-BxS5hiG2.js +0 -1
- package/dist/assets/state-_Qai_QyX.js +0 -3
- package/dist/assets/tooltip-mT3CGest.js +0 -61
- package/dist/assets/tracing-Di6pUmX8.js +0 -1
- package/dist/assets/useAsyncData-C7aJJz0Y.js +0 -1
- package/dist/assets/useNotebookActions-DIc4xTzY.js +0 -1
- package/dist/assets/vega-component-CnadTgoE.js +0 -1
- package/dist/assets/vega-loader.browser-CLRkNgm7.js +0 -6
- package/src/core/websocket/useWebSocket.tsx +0 -88
|
@@ -174,8 +174,11 @@ describe("notebookStateFromSession", () => {
|
|
|
174
174
|
channel: "marimo-error",
|
|
175
175
|
data: [
|
|
176
176
|
{
|
|
177
|
-
type: "
|
|
177
|
+
type: "exception",
|
|
178
|
+
exception_type: "ValueError",
|
|
178
179
|
msg: "Something went wrong",
|
|
180
|
+
raising_cell: null,
|
|
181
|
+
traceback: "Traceback line 1",
|
|
179
182
|
},
|
|
180
183
|
],
|
|
181
184
|
mimetype: "application/vnd.marimo+error",
|
|
@@ -183,6 +186,52 @@ describe("notebookStateFromSession", () => {
|
|
|
183
186
|
});
|
|
184
187
|
});
|
|
185
188
|
|
|
189
|
+
it("restores matching code from the session", () => {
|
|
190
|
+
const code = "raise ValueError('cached failure')";
|
|
191
|
+
const session = createSession([
|
|
192
|
+
createSessionCell(
|
|
193
|
+
"cell-1",
|
|
194
|
+
[
|
|
195
|
+
{
|
|
196
|
+
type: "error",
|
|
197
|
+
ename: "ValueError",
|
|
198
|
+
evalue: "cached failure",
|
|
199
|
+
traceback: ["Traceback\n", "ValueError: cached failure"],
|
|
200
|
+
code,
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
[
|
|
204
|
+
{
|
|
205
|
+
type: "stream",
|
|
206
|
+
name: "stderr",
|
|
207
|
+
text: "ValueError: cached <failure>",
|
|
208
|
+
mimetype: "application/vnd.marimo+traceback",
|
|
209
|
+
traceback_format: "text",
|
|
210
|
+
code,
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
),
|
|
214
|
+
]);
|
|
215
|
+
const state = notebookStateFromSession(session, null);
|
|
216
|
+
invariant(state, "state is null");
|
|
217
|
+
expect(state.cellRuntime[CELL_1].output).toMatchObject({
|
|
218
|
+
code,
|
|
219
|
+
data: [
|
|
220
|
+
{
|
|
221
|
+
type: "exception",
|
|
222
|
+
exception_type: "ValueError",
|
|
223
|
+
msg: "cached failure",
|
|
224
|
+
traceback: "Traceback\nValueError: cached failure",
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
});
|
|
228
|
+
expect(state.cellRuntime[CELL_1].consoleOutputs[0].code).toBe(code);
|
|
229
|
+
expect(state.cellRuntime[CELL_1].consoleOutputs[0]).toMatchObject({
|
|
230
|
+
traceback_format: "text",
|
|
231
|
+
data: "ValueError: cached <failure>",
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
186
235
|
it("handles data output in session cell", () => {
|
|
187
236
|
const dataOutput = {
|
|
188
237
|
type: "data" as const,
|
package/src/core/cells/cells.ts
CHANGED
|
@@ -1761,7 +1761,8 @@ export const cellErrorsAtom = atom((get) => {
|
|
|
1761
1761
|
});
|
|
1762
1762
|
|
|
1763
1763
|
export const notebookOutline = atom((get) => {
|
|
1764
|
-
const
|
|
1764
|
+
const cellIds = get(cellIdsAtom);
|
|
1765
|
+
const cellRuntime = get(cellsRuntimeAtom);
|
|
1765
1766
|
const outlines = cellIds.inOrderIds.map(
|
|
1766
1767
|
(cellId) => cellRuntime[cellId].outline,
|
|
1767
1768
|
);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describeError } from "@/core/errors/error-entries";
|
|
4
|
+
import type { MarimoError } from "@/core/kernel/messages";
|
|
5
|
+
import { sanitizeHtml } from "@/plugins/core/sanitize-html";
|
|
6
|
+
import { parseHtmlContent } from "@/utils/dom";
|
|
7
|
+
import type { CellId } from "./ids";
|
|
8
|
+
|
|
9
|
+
export interface CellOutputMessage<Output> {
|
|
10
|
+
type: "cell-output";
|
|
11
|
+
cell_id: CellId;
|
|
12
|
+
code?: string;
|
|
13
|
+
output: Output;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ErrorMessageOutput {
|
|
17
|
+
type: "error";
|
|
18
|
+
instructions: string;
|
|
19
|
+
message: string;
|
|
20
|
+
traceback?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const ERROR_MESSAGE_INSTRUCTIONS = "Fix this notebook error.";
|
|
24
|
+
|
|
25
|
+
export function buildErrorReport(
|
|
26
|
+
cellId: CellId,
|
|
27
|
+
evidence: { code?: string } & (
|
|
28
|
+
| { errors: MarimoError[] }
|
|
29
|
+
| { traceback: string; tracebackFormat: "html" | "text" }
|
|
30
|
+
),
|
|
31
|
+
): CellOutputMessage<ErrorMessageOutput> {
|
|
32
|
+
let message: string;
|
|
33
|
+
let traceback: string;
|
|
34
|
+
if ("errors" in evidence) {
|
|
35
|
+
message = evidence.errors.map(describeError).join("\n");
|
|
36
|
+
traceback = evidence.errors
|
|
37
|
+
.flatMap((error) =>
|
|
38
|
+
"traceback" in error && error.traceback
|
|
39
|
+
? [parseHtmlContent(sanitizeHtml(error.traceback))]
|
|
40
|
+
: [],
|
|
41
|
+
)
|
|
42
|
+
.join("\n\n");
|
|
43
|
+
} else {
|
|
44
|
+
traceback =
|
|
45
|
+
evidence.tracebackFormat === "html"
|
|
46
|
+
? parseHtmlContent(sanitizeHtml(evidence.traceback))
|
|
47
|
+
: evidence.traceback;
|
|
48
|
+
message = traceback.split("\n").findLast((line) => line.length > 0) ?? "";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Put repair intent before evidence so it survives prefix truncation.
|
|
52
|
+
return {
|
|
53
|
+
type: "cell-output",
|
|
54
|
+
cell_id: cellId,
|
|
55
|
+
output: {
|
|
56
|
+
type: "error",
|
|
57
|
+
instructions: ERROR_MESSAGE_INSTRUCTIONS,
|
|
58
|
+
message,
|
|
59
|
+
...(traceback ? { traceback } : {}),
|
|
60
|
+
},
|
|
61
|
+
...(evidence.code === undefined ? {} : { code: evidence.code }),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -134,12 +134,8 @@ export function usePendingDeleteService() {
|
|
|
134
134
|
submit({ cellIds, deleteCell, deleteManyCells, store });
|
|
135
135
|
},
|
|
136
136
|
clear,
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
},
|
|
140
|
-
get shouldConfirmDelete() {
|
|
141
|
-
return entries.size > 1;
|
|
142
|
-
},
|
|
137
|
+
idle: entries.size === 0,
|
|
138
|
+
shouldConfirmDelete: entries.size > 1,
|
|
143
139
|
};
|
|
144
140
|
}
|
|
145
141
|
|
|
@@ -206,8 +206,20 @@ function createCellRuntimeFromSession(
|
|
|
206
206
|
channel: "marimo-error",
|
|
207
207
|
data: [
|
|
208
208
|
{
|
|
209
|
-
type: "
|
|
209
|
+
type: "exception",
|
|
210
|
+
exception_type: primaryOutput.ename,
|
|
210
211
|
msg: primaryOutput.evalue,
|
|
212
|
+
raising_cell: null,
|
|
213
|
+
traceback:
|
|
214
|
+
primaryOutput.traceback.length === 0
|
|
215
|
+
? null
|
|
216
|
+
: primaryOutput.traceback
|
|
217
|
+
.map((line, index, lines) =>
|
|
218
|
+
index < lines.length - 1 && !line.endsWith("\n")
|
|
219
|
+
? `${line}\n`
|
|
220
|
+
: line,
|
|
221
|
+
)
|
|
222
|
+
.join(""),
|
|
211
223
|
},
|
|
212
224
|
],
|
|
213
225
|
mimetype: "application/vnd.marimo+error",
|
|
@@ -223,6 +235,9 @@ function createCellRuntimeFromSession(
|
|
|
223
235
|
timestamp: DEFAULT_TIMESTAMP,
|
|
224
236
|
};
|
|
225
237
|
}
|
|
238
|
+
if (runtimeState.output && primaryOutput.code !== undefined) {
|
|
239
|
+
runtimeState.output.code = primaryOutput.code;
|
|
240
|
+
}
|
|
226
241
|
}
|
|
227
242
|
|
|
228
243
|
const consoleOutputs = sessionCell.console || [];
|
|
@@ -237,7 +252,13 @@ function createCellRuntimeFromSession(
|
|
|
237
252
|
channel: "media",
|
|
238
253
|
data: consoleOutput.data,
|
|
239
254
|
mimetype: consoleOutput.mimetype,
|
|
255
|
+
...(consoleOutput.traceback_format === undefined
|
|
256
|
+
? {}
|
|
257
|
+
: { traceback_format: consoleOutput.traceback_format }),
|
|
240
258
|
timestamp: DEFAULT_TIMESTAMP,
|
|
259
|
+
...(consoleOutput.code === undefined
|
|
260
|
+
? {}
|
|
261
|
+
: { code: consoleOutput.code }),
|
|
241
262
|
};
|
|
242
263
|
}
|
|
243
264
|
// Handle StreamOutput (type: "stream")
|
|
@@ -245,7 +266,13 @@ function createCellRuntimeFromSession(
|
|
|
245
266
|
channel: consoleOutput.name === "stderr" ? "stderr" : "stdout",
|
|
246
267
|
data: consoleOutput.text,
|
|
247
268
|
mimetype: consoleOutput.mimetype ?? "text/plain",
|
|
269
|
+
...(consoleOutput.traceback_format === undefined
|
|
270
|
+
? {}
|
|
271
|
+
: { traceback_format: consoleOutput.traceback_format }),
|
|
248
272
|
timestamp: DEFAULT_TIMESTAMP,
|
|
273
|
+
...(consoleOutput.code === undefined
|
|
274
|
+
? {}
|
|
275
|
+
: { code: consoleOutput.code }),
|
|
249
276
|
};
|
|
250
277
|
}),
|
|
251
278
|
};
|
package/src/core/config/pair.ts
CHANGED
|
@@ -3,16 +3,18 @@
|
|
|
3
3
|
import { atom } from "jotai";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
|
-
export const
|
|
6
|
+
export const pairConfigSchema = z.object({
|
|
7
7
|
command: z.string(),
|
|
8
|
+
hostEnabled: z.boolean().default(false),
|
|
8
9
|
templates: z.object({
|
|
9
10
|
prompt: z.string(),
|
|
11
|
+
notebook: z.string(),
|
|
10
12
|
session: z.string(),
|
|
11
13
|
token_file: z.string(),
|
|
12
14
|
token: z.string(),
|
|
13
15
|
}),
|
|
14
16
|
});
|
|
15
17
|
|
|
16
|
-
export type
|
|
18
|
+
export type PairConfig = z.infer<typeof pairConfigSchema>;
|
|
17
19
|
|
|
18
|
-
export const
|
|
20
|
+
export const pairConfigAtom = atom<PairConfig | undefined>(undefined);
|
|
@@ -185,8 +185,8 @@ describe("isDetectedSourceConnected", () => {
|
|
|
185
185
|
});
|
|
186
186
|
const emptySnapshot = {
|
|
187
187
|
dialects: [] as string[],
|
|
188
|
-
storageProtocols:
|
|
189
|
-
storageBackendTypes:
|
|
188
|
+
storageProtocols: new Set<string>(),
|
|
189
|
+
storageBackendTypes: new Set<string>(),
|
|
190
190
|
};
|
|
191
191
|
|
|
192
192
|
it("does not hide a source when nothing is connected", () => {
|
|
@@ -242,7 +242,7 @@ describe("isDetectedSourceConnected", () => {
|
|
|
242
242
|
expect(
|
|
243
243
|
isDetectedSourceConnected(s3, {
|
|
244
244
|
...emptySnapshot,
|
|
245
|
-
storageProtocols: ["s3"],
|
|
245
|
+
storageProtocols: new Set(["s3"]),
|
|
246
246
|
}),
|
|
247
247
|
).toBe(true);
|
|
248
248
|
});
|
|
@@ -262,7 +262,7 @@ describe("isDetectedSourceConnected", () => {
|
|
|
262
262
|
expect(
|
|
263
263
|
isDetectedSourceConnected(hf, {
|
|
264
264
|
...emptySnapshot,
|
|
265
|
-
storageBackendTypes: ["huggingface"],
|
|
265
|
+
storageBackendTypes: new Set(["huggingface"]),
|
|
266
266
|
}),
|
|
267
267
|
).toBe(true);
|
|
268
268
|
});
|
|
@@ -27,8 +27,8 @@ export function matchesDiscoveryGroup(
|
|
|
27
27
|
*/
|
|
28
28
|
export interface LiveConnectionSnapshot {
|
|
29
29
|
dialects: readonly string[];
|
|
30
|
-
storageProtocols:
|
|
31
|
-
storageBackendTypes:
|
|
30
|
+
storageProtocols: ReadonlySet<string>;
|
|
31
|
+
storageBackendTypes: ReadonlySet<string>;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
@@ -47,15 +47,13 @@ export function isDetectedSourceConnected(
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
if (
|
|
50
|
-
match.protocols.some((protocol) =>
|
|
51
|
-
snapshot.storageProtocols.includes(protocol),
|
|
52
|
-
)
|
|
50
|
+
match.protocols.some((protocol) => snapshot.storageProtocols.has(protocol))
|
|
53
51
|
) {
|
|
54
52
|
return true;
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
return match.backendTypes.some((backendType) =>
|
|
58
|
-
snapshot.storageBackendTypes.
|
|
56
|
+
snapshot.storageBackendTypes.has(backendType),
|
|
59
57
|
);
|
|
60
58
|
}
|
|
61
59
|
|
package/src/core/edit-app.tsx
CHANGED
|
@@ -41,7 +41,7 @@ import { viewStateAtom } from "./mode";
|
|
|
41
41
|
import { useRequestClient } from "./network/requests";
|
|
42
42
|
import { useFilename } from "./saving/filename";
|
|
43
43
|
import { lastSavedNotebookAtom } from "./saving/state";
|
|
44
|
-
import {
|
|
44
|
+
import { useNotebookSession } from "./websocket/useNotebookSession";
|
|
45
45
|
|
|
46
46
|
interface AppProps {
|
|
47
47
|
/**
|
|
@@ -83,7 +83,7 @@ export const EditApp: React.FC<AppProps> = ({
|
|
|
83
83
|
};
|
|
84
84
|
}, []);
|
|
85
85
|
|
|
86
|
-
const { connection, reconnect } =
|
|
86
|
+
const { connection, reconnect } = useNotebookSession({
|
|
87
87
|
autoInstantiate: userConfig.runtime.auto_instantiate,
|
|
88
88
|
setCells: (cells, layout) => {
|
|
89
89
|
setCells(cells);
|
package/src/core/host/AGENTS.md
CHANGED
|
@@ -4,3 +4,8 @@ Production code may import host modules, `jotai/vanilla`, `zod`, and
|
|
|
4
4
|
`@/utils/Logger`. Enforced by `marimo/host-imports`. Tests are exempt.
|
|
5
5
|
|
|
6
6
|
Define interfaces here and inject application behavior through adapters.
|
|
7
|
+
|
|
8
|
+
`stream.ts` reduces stream events into one state. Each kind of object is a
|
|
9
|
+
`Resource` in `resources/` with pure handlers keyed by event name. The stream
|
|
10
|
+
handles phase, notebook identity, and control events itself. The client holds
|
|
11
|
+
one state atom and exposes derived atoms.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { attachments } from "../resources/attachments";
|
|
5
|
+
import { createStream } from "../stream";
|
|
6
|
+
import { fixtureEvents } from "./fixtures";
|
|
7
|
+
|
|
8
|
+
const stream = createStream({ attachments });
|
|
9
|
+
const snapshot = fixtureEvents("notebook-events.sse");
|
|
10
|
+
const records = snapshot.flatMap((event) =>
|
|
11
|
+
event.event === "attachment" ? [event.data.attachment] : [],
|
|
12
|
+
);
|
|
13
|
+
const confirmed = () => snapshot.reduce(stream.reduce, stream.initial);
|
|
14
|
+
|
|
15
|
+
describe("attachment resource", () => {
|
|
16
|
+
it("collects snapshot records, replaces by id, and removes attachments", () => {
|
|
17
|
+
const state = confirmed();
|
|
18
|
+
expect(state.resources.attachments).toEqual(records);
|
|
19
|
+
const [browser, agent] = records;
|
|
20
|
+
const renamed = { ...agent, name: "Renamed agent" };
|
|
21
|
+
const updated = stream.reduce(state, {
|
|
22
|
+
event: "attachment",
|
|
23
|
+
id: "98",
|
|
24
|
+
data: { attachment: renamed },
|
|
25
|
+
});
|
|
26
|
+
expect(updated.resources.attachments).toEqual([browser, renamed]);
|
|
27
|
+
expect(state.resources.attachments).toEqual(records);
|
|
28
|
+
|
|
29
|
+
const removed = stream.reduce(updated, {
|
|
30
|
+
event: "attachment.removed",
|
|
31
|
+
id: "99",
|
|
32
|
+
data: { id: browser.id, time: "2026-10-01T09:30:00Z" },
|
|
33
|
+
});
|
|
34
|
+
expect(removed.resources.attachments).toEqual([renamed]);
|
|
35
|
+
const repeated = stream.reduce(removed, {
|
|
36
|
+
event: "attachment.removed",
|
|
37
|
+
id: "100",
|
|
38
|
+
data: { id: browser.id, time: "2026-10-01T09:30:00Z" },
|
|
39
|
+
});
|
|
40
|
+
expect(repeated.resources.attachments).toBe(removed.resources.attachments);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("retains records for replay and rebuilds them after reset", () => {
|
|
44
|
+
const state = confirmed();
|
|
45
|
+
expect(stream.resume(state)).toBe(state);
|
|
46
|
+
const reset = stream.reduce(state, { event: "reset", data: {} });
|
|
47
|
+
expect(reset.resources.attachments).toEqual([]);
|
|
48
|
+
const replacement = snapshot
|
|
49
|
+
.filter(
|
|
50
|
+
(event) =>
|
|
51
|
+
event.event !== "attachment" ||
|
|
52
|
+
event.data.attachment.id === records[0].id,
|
|
53
|
+
)
|
|
54
|
+
.reduce(stream.reduce, reset);
|
|
55
|
+
expect(replacement.resources.attachments).toEqual([records[0]]);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import type { NotebookEvent } from "../events";
|
|
5
|
+
import { document } from "../resources/document";
|
|
6
|
+
import { createStream } from "../stream";
|
|
7
|
+
import { fixtureEvents } from "./fixtures";
|
|
8
|
+
|
|
9
|
+
const stream = createStream({ document });
|
|
10
|
+
const snapshot = fixtureEvents("notebook-events.sse");
|
|
11
|
+
const edits = fixtureEvents("edits.sse");
|
|
12
|
+
|
|
13
|
+
function replay(events: readonly NotebookEvent[], from = stream.initial) {
|
|
14
|
+
return events.reduce((state, event) => stream.reduce(state, event), from);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const confirmed = () => replay(snapshot);
|
|
18
|
+
const current = (state: ReturnType<typeof replay>) =>
|
|
19
|
+
state.resources.document.current;
|
|
20
|
+
|
|
21
|
+
describe("document resource", () => {
|
|
22
|
+
it("publishes the snapshot at ready, then each completed edit and save", () => {
|
|
23
|
+
const ready = snapshot.findIndex((event) => event.event === "ready");
|
|
24
|
+
const opening = replay(snapshot.slice(0, ready));
|
|
25
|
+
expect(current(opening)).toBeNull();
|
|
26
|
+
const first = current(replay(snapshot.slice(ready), opening));
|
|
27
|
+
expect(first?.revision.order).toEqual(["c1", "c3", "k7f2"]);
|
|
28
|
+
expect([...first!.cells.keys()]).toEqual(first?.revision.order);
|
|
29
|
+
|
|
30
|
+
const [edit, save] = edits.filter((event) => event.event === "document");
|
|
31
|
+
const pending = replay(
|
|
32
|
+
edits.filter((event) => event.event !== "document"),
|
|
33
|
+
confirmed(),
|
|
34
|
+
);
|
|
35
|
+
expect(current(pending)).toEqual(first);
|
|
36
|
+
const changed = stream.reduce(pending, edit);
|
|
37
|
+
expect(current(changed)?.revision).toEqual(edit.data.document);
|
|
38
|
+
expect([...current(changed)!.cells.keys()].toSorted()).toEqual(
|
|
39
|
+
edit.data.document.order.toSorted(),
|
|
40
|
+
);
|
|
41
|
+
const saved = current(stream.reduce(changed, save));
|
|
42
|
+
expect(saved?.revision).toEqual(save.data.document);
|
|
43
|
+
expect(saved?.cells.get("c3")?.code).toBe("x = 22");
|
|
44
|
+
expect(saved?.cells.has("k7f2")).toBe(false);
|
|
45
|
+
expect(saved?.revision.saved_etag).toBe(saved?.revision.etag);
|
|
46
|
+
expect(first?.cells.get("c3")?.code).toBe("x = 21");
|
|
47
|
+
expect(first?.cells.has("k7f2")).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("applies order-only changes and distinguishes an empty notebook from no document", () => {
|
|
51
|
+
const state = confirmed();
|
|
52
|
+
const revision = current(state)!.revision;
|
|
53
|
+
const reordered = {
|
|
54
|
+
...revision,
|
|
55
|
+
order: revision.order.toReversed(),
|
|
56
|
+
version: revision.version + 1,
|
|
57
|
+
};
|
|
58
|
+
const changed = stream.reduce(state, {
|
|
59
|
+
event: "document",
|
|
60
|
+
data: { document: reordered },
|
|
61
|
+
id: "200",
|
|
62
|
+
});
|
|
63
|
+
expect(current(changed)).toEqual({
|
|
64
|
+
revision: reordered,
|
|
65
|
+
cells: current(state)?.cells,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const empty = { ...revision, order: [] };
|
|
69
|
+
const opening = stream.reduce(stream.initial, {
|
|
70
|
+
event: "document",
|
|
71
|
+
data: { document: empty },
|
|
72
|
+
});
|
|
73
|
+
expect(current(opening)).toBeNull();
|
|
74
|
+
expect(
|
|
75
|
+
current(stream.reduce(opening, { event: "ready", data: {}, id: "0" })),
|
|
76
|
+
).toEqual({ revision: empty, cells: new Map() });
|
|
77
|
+
expect(
|
|
78
|
+
current(
|
|
79
|
+
stream.reduce(stream.initial, { event: "ready", data: {}, id: "0" }),
|
|
80
|
+
),
|
|
81
|
+
).toBeNull();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("keeps the document while a live update is pending and after a replay completes it", () => {
|
|
85
|
+
const state = confirmed();
|
|
86
|
+
const [cell, , removed, published] = edits;
|
|
87
|
+
if (published.event !== "document") {
|
|
88
|
+
throw new Error("Fixture does not publish a document");
|
|
89
|
+
}
|
|
90
|
+
const partial = replay([cell, removed], state);
|
|
91
|
+
expect(current(partial)).toBe(current(state));
|
|
92
|
+
expect(stream.resume(partial)).toBe(partial);
|
|
93
|
+
const completed = replay(
|
|
94
|
+
[published, { event: "ready", data: {}, id: "113" }],
|
|
95
|
+
partial,
|
|
96
|
+
);
|
|
97
|
+
expect(current(completed)?.revision).toEqual(published.data.document);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("removes a notebook without waiting for individual cell removals", () => {
|
|
101
|
+
const removed = stream.reduce(confirmed(), {
|
|
102
|
+
event: "notebook.removed",
|
|
103
|
+
data: { id: "n1", time: "2026-10-01T09:30:00Z" },
|
|
104
|
+
id: "200",
|
|
105
|
+
});
|
|
106
|
+
expect(current(removed)).toBeNull();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("rejects records that cannot form a complete revision", () => {
|
|
110
|
+
const state = confirmed();
|
|
111
|
+
const revision = current(state)!.revision;
|
|
112
|
+
expect(() =>
|
|
113
|
+
stream.reduce(state, {
|
|
114
|
+
event: "document",
|
|
115
|
+
data: { document: { ...revision, order: ["missing"] } },
|
|
116
|
+
id: "200",
|
|
117
|
+
}),
|
|
118
|
+
).toThrow("Document records do not match");
|
|
119
|
+
const partial = stream.reduce(state, edits[0]);
|
|
120
|
+
expect(() =>
|
|
121
|
+
stream.reduce(partial, { event: "ready", data: {}, id: "200" }),
|
|
122
|
+
).toThrow("incomplete document update");
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { parseNotebookEvent } from "../events";
|
|
6
|
+
|
|
7
|
+
describe("notebook messages", () => {
|
|
8
|
+
it("normalizes session and host envelopes for native marimo messages", () => {
|
|
9
|
+
const notification = {
|
|
10
|
+
op: "variables",
|
|
11
|
+
variables: [{ name: "x", value: Number.NaN }],
|
|
12
|
+
};
|
|
13
|
+
const host = { event: "marimo", data: { marimo: notification } };
|
|
14
|
+
expect(parseNotebookEvent({ data: notification })).toEqual(host);
|
|
15
|
+
expect(parseNotebookEvent({ ...host, id: "42" })).toEqual({
|
|
16
|
+
...host,
|
|
17
|
+
id: "42",
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("retains unknown event cursors without interpreting their payloads", () => {
|
|
22
|
+
expect(
|
|
23
|
+
parseNotebookEvent({ event: "future", id: "42", data: null }),
|
|
24
|
+
).toEqual({ event: "unknown", name: "future", id: "42" });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("decodes known events outside the client's subscription filters", () => {
|
|
28
|
+
const event = {
|
|
29
|
+
event: "run.removed",
|
|
30
|
+
id: "42",
|
|
31
|
+
data: { id: "r1", time: "2026-10-19T12:00:00Z" },
|
|
32
|
+
};
|
|
33
|
+
expect(parseNotebookEvent(event)).toEqual(event);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it.each([
|
|
37
|
+
{ event: "cell", id: "42", data: { cell: { id: "c1" } } },
|
|
38
|
+
{ event: "run.removed", id: "42", data: {} },
|
|
39
|
+
{ event: "ready", data: {} },
|
|
40
|
+
{ event: "marimo", data: {} },
|
|
41
|
+
{},
|
|
42
|
+
null,
|
|
43
|
+
])("rejects malformed known messages: %j", (message) => {
|
|
44
|
+
expect(() => parseNotebookEvent(message)).toThrow(z.ZodError);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import NodeFs from "node:fs";
|
|
4
|
+
import NodeUrl from "node:url";
|
|
5
|
+
import { parseNotebookEvent, type NotebookEvent } from "../events";
|
|
6
|
+
|
|
7
|
+
/** Read the protocol's portable examples as transport messages. */
|
|
8
|
+
export function fixtureMessages(name: string) {
|
|
9
|
+
const path = new NodeUrl.URL(
|
|
10
|
+
`../../../../../protocols/host/fixtures/${name}`,
|
|
11
|
+
import.meta.url,
|
|
12
|
+
);
|
|
13
|
+
return NodeFs.readFileSync(path, "utf8")
|
|
14
|
+
.split(/\r?\n\r?\n/)
|
|
15
|
+
.flatMap((frame) => {
|
|
16
|
+
const event = /^event: (.+)$/m.exec(frame)?.[1];
|
|
17
|
+
if (!event) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
const data = frame
|
|
21
|
+
.split(/\r?\n/)
|
|
22
|
+
.filter((line) => line.startsWith("data:"))
|
|
23
|
+
.map((line) => line.slice(5).trimStart())
|
|
24
|
+
.join("\n");
|
|
25
|
+
const payload: unknown = JSON.parse(data);
|
|
26
|
+
return [
|
|
27
|
+
{
|
|
28
|
+
event,
|
|
29
|
+
id: /^id: (.+)$/m.exec(frame)?.[1],
|
|
30
|
+
data: payload,
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Read the protocol's portable examples as decoded notebook events. */
|
|
37
|
+
export function fixtureEvents(name: string): NotebookEvent[] {
|
|
38
|
+
return fixtureMessages(name).map(parseNotebookEvent);
|
|
39
|
+
}
|