@marimo-team/frontend 0.25.3-dev10 → 0.25.3-dev101
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-fK4n2NPc.js +1 -0
- package/dist/assets/{ConnectedDataExplorerComponent-t8fH_TNc.js → ConnectedDataExplorerComponent-Dft8R6l5.js} +1 -1
- package/dist/assets/{ErrorBoundary-B4n9EnHB.js → ErrorBoundary-CAEGdoFW.js} +2 -2
- package/dist/assets/JsonOutput-CyAKmZDD.js +11 -0
- package/dist/assets/{LazyAnyLanguageCodeMirror-CKcCbG2G.js → LazyAnyLanguageCodeMirror-BPYE76jR.js} +2 -2
- package/dist/assets/MarimoErrorOutput-CgZvP0p0.js +10 -0
- package/dist/assets/{RenderHTML-9bMgbYmo.js → RenderHTML-DQvxDDNS.js} +1 -1
- package/dist/assets/RunButton-BBDnDL6G.js +1 -0
- package/dist/assets/__vite-browser-external-8vDMR0Kk.js +1 -0
- package/dist/assets/__vite-browser-external-mKcnhO20.js +1 -0
- package/dist/assets/{add-cell-with-ai-f_MnLCwI.js → add-cell-with-ai-Clg5FELx.js} +7 -7
- package/dist/assets/{add-connection-dialog-CoE_PhuD.js → add-connection-dialog-BqdWmD-7.js} +2 -2
- package/dist/assets/{agent-panel-BWZz24jK.js → agent-panel-5RxG5Y82.js} +3 -3
- package/dist/assets/ai-model-dropdown-Bvl1ktQq.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-BhU0ulYs.js → app-config-button-0QTeQ2Mv.js} +1 -1
- package/dist/assets/arrays-0R2reF2y.js +1 -0
- package/dist/assets/{blob-iOBwGaP9.js → blob-5VcB8K_t.js} +1 -1
- package/dist/assets/{button-COI-NGrx.js → button-D8gVKwe-.js} +1 -1
- package/dist/assets/{cache-panel-DTY1dqvi.js → cache-panel-BV-c7ed0.js} +1 -1
- package/dist/assets/{capabilities-BJvf-MD4.js → capabilities-Pq1YNPlK.js} +1 -1
- package/dist/assets/cell-editor-Byw5y-oI.js +42 -0
- package/dist/assets/cell-link-C5yYyzuF.js +1 -0
- package/dist/assets/{cells-rA_jsZCj.js → cells-D_11MjVB.js} +60 -60
- package/dist/assets/{chat-display-BmEPmhDP.js → chat-display-BCR_ySiV.js} +1 -1
- package/dist/assets/chat-panel-RPRvBsaU.js +4 -0
- package/dist/assets/chat-ui-CGviVaRA.js +4 -0
- package/dist/assets/{circle-DXf-VWZG.js → circle-B6D_6D8w.js} +1 -1
- package/dist/assets/code-visibility-DYDagVy7.js +1 -0
- package/dist/assets/coerce-DWjFeXOY.js +1 -0
- package/dist/assets/column-preview-xe533-Cn.js +1 -0
- package/dist/assets/command-palette-BB9bWdWp.js +1 -0
- package/dist/assets/{common-lisZ0l6a.js → common-CaeItbcI.js} +1 -1
- package/dist/assets/components-CBE8cOLK.js +1 -0
- package/dist/assets/{components-CP_UoFtT.js → components-DCo7mAWO.js} +1 -1
- package/dist/assets/{components-Dvd1GnD6.js → components-G6frbw0c.js} +1 -1
- package/dist/assets/config-CD1IuZFJ.js +1 -0
- package/dist/assets/{config-DJLAC21v.js → config-DMz7oN0r.js} +1 -1
- package/dist/assets/config-DpY86_Yq.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-CaI_Oa0v.js} +1 -1
- package/dist/assets/{datasource-Dr1xNKUn.js → datasource-deuXnlU-.js} +1 -1
- package/dist/assets/{dates-CfmdTL6B.js → dates-CQMwFko2.js} +1 -1
- package/dist/assets/{dependency-graph-panel-B0uP-_id.js → dependency-graph-panel-CIykLnJe.js} +2 -2
- package/dist/assets/{dist-iaThVDkA.js → dist-jlhMTKo_.js} +1 -1
- package/dist/assets/{documentation-panel-DVl_ebWy.js → documentation-panel-2PQ8kQLa.js} +1 -1
- package/dist/assets/{download-Bx55LMki.js → download-dK52bLxZ.js} +1 -1
- package/dist/assets/{dropdown-menu-C1rnl-0o.js → dropdown-menu-DS6umAnH.js} +1 -1
- package/dist/assets/edit-page-DrElzw10.js +9 -0
- package/dist/assets/{error-banner-Cd69vvyX.js → error-banner-B5kzVf_0.js} +1 -1
- package/dist/assets/{error-entries-DOWpdGvI.js → error-entries-3GVk2KjS.js} +1 -1
- package/dist/assets/{error-panel-DpbNKSLs.js → error-panel-CzDRGPeC.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-B99ThNJW.js → feedback-button-C5NgWpFg.js} +2 -2
- package/dist/assets/feedback-button-CONwLFpw.js +1 -0
- package/dist/assets/file-explorer-panel-B50xDnTZ.js +1 -0
- package/dist/assets/{file-name-input-ChnBsDE_.js → file-name-input-DqwMSgGU.js} +1 -1
- package/dist/assets/{floating-outline-MYqQW4Vp.js → floating-outline-ChmAuvxz.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-DSL0J4md.js → gallery-page-CiL8aMS1.js} +1 -1
- package/dist/assets/{ganttDiagram-EL5Y4UJY-2n7Ig5Jm.js → ganttDiagram-EL5Y4UJY-CYqkCfFG.js} +1 -1
- package/dist/assets/{glide-data-editor-B1MWlKeM.js → glide-data-editor-CqRclBkp.js} +2 -2
- package/dist/assets/{glide-utils-CIrAxdjv.js → glide-utils-HFqhvWHI.js} +2 -2
- package/dist/assets/home-page-BgZVl4dP.js +1 -0
- package/dist/assets/{hooks-BIogTGCr.js → hooks-DM5n7Osw.js} +1 -1
- package/dist/assets/{html-to-image-DK9Zl1lU.js → html-to-image-D_0rd3_B.js} +1 -1
- package/dist/assets/index-BnhSnwvB.css +2 -0
- package/dist/assets/index-EG2n8Oht.js +30 -0
- package/dist/assets/{input-BoBxR50a.js → input-DmHMEZGh.js} +1 -1
- package/dist/assets/kiosk-mode-DFS6HopF.js +1 -0
- package/dist/assets/layout-BpP8nTjb.js +9 -0
- package/dist/assets/linear-Bma8J3iM.js +1 -0
- package/dist/assets/links-DX2w97N9.js +1 -0
- package/dist/assets/{logs-panel-y0XnfulJ.js → logs-panel-B5eVAZtH.js} +1 -1
- package/dist/assets/{markdown-renderer-Q5AhbRU3.js → markdown-renderer-BWUzqkJM.js} +1 -1
- package/dist/assets/{menu-items-BrwnTgyK.js → menu-items-BgUyNUpu.js} +1 -1
- package/dist/assets/{mermaid-a3xYymTy.js → mermaid-BKXlWk4F.js} +2 -2
- package/dist/assets/{name-cell-input-BNOnF6-0.js → name-cell-input-DXW68gRW.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-Be7stWwv.js → outline-panel-DPxJhJ_A.js} +1 -1
- package/dist/assets/{package-data-dv4FBHxR.js → package-data-CKPmBDtg.js} +1 -1
- package/dist/assets/packages-panel-gBE2FWQr.js +1 -0
- package/dist/assets/{panel-accordion-state-GeMEdCPw.js → panel-accordion-state-CCF5Gxnc.js} +1 -1
- package/dist/assets/panels-Dr6qMCvU.js +105 -0
- package/dist/assets/precisionRound-BxlUs8G4.js +1 -0
- package/dist/assets/process-output-Cw9IDcKQ.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-YULOXf4h.js → readonly-code-display-DYcZQ28T.js} +1 -1
- package/dist/assets/{readonly-python-code-CadgXgRu.js → readonly-python-code-RFuQP50v.js} +1 -1
- package/dist/assets/{renderShortcut-BkVJQ_Zh.js → renderShortcut-rUwGqKgC.js} +1 -1
- package/dist/assets/{requests-lt0JK8Jt.js → requests-BIFR-h4M.js} +1 -1
- package/dist/assets/requests-DuLJN9Tf.js +1 -0
- package/dist/assets/{reveal-component-BVAR7ayZ.js → reveal-component-yRrDmCQz.js} +37 -37
- package/dist/assets/run-page-B69M4-zE.js +1 -0
- package/dist/assets/{runs-BLp5jezv.js → runs-C49hleBy.js} +1 -1
- package/dist/assets/{runtime-details-state-DuChzsA6.js → runtime-details-state-pobtu6TO.js} +1 -1
- package/dist/assets/{save-worker-BbUSwZRG.js → save-worker-pCkSHhuz.js} +1 -1
- package/dist/assets/{schemas-Dv42wLFq.js → schemas-ChRqMOTL.js} +1 -1
- package/dist/assets/scratchpad-panel-H44_pvb_.js +1 -0
- 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-bcfEKxRx.js} +1 -1
- package/dist/assets/{select-DTBn5eTb.js → select-3CVGlYrC.js} +1 -1
- package/dist/assets/{session-DqoWmb7X.js → session-CMLIzZKw.js} +1 -1
- package/dist/assets/session-panel-Bnd1iXkO.js +1 -0
- package/dist/assets/{snippets-panel-CQ1dn3lA.js → snippets-panel-DKApxKx9.js} +1 -1
- package/dist/assets/{spec-BfSBcO5B.js → spec-uCpVvaAR.js} +1 -1
- package/dist/assets/{state-uwRvJLb8.js → state-3mcyTS9q.js} +1 -1
- package/dist/assets/state-B_MCKzBL.js +1 -0
- package/dist/assets/state-Bl9BiY_s.js +3 -0
- package/dist/assets/{state-C3oDoyFe.js → state-C5WAeuTu.js} +1 -1
- package/dist/assets/{state-DxQnC_1I.js → state-Cv2JW3l9.js} +1 -1
- package/dist/assets/{state-Bfh_2SLl.js → state-Dv059B3A.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-DYjBfpNJ.js} +1 -1
- package/dist/assets/{tabs-BuQlZFbt.js → tabs-CY8E4r9U.js} +1 -1
- package/dist/assets/{terminal-Ck_vK-34.js → terminal-Ca-kubKb.js} +1 -1
- 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-Clwy4SlW.js +1 -0
- package/dist/assets/tracing-panel-BwjLG_aC.js +2 -0
- package/dist/assets/{tree-actions-DAd80MbP.js → tree-actions-BZNTQ12N.js} +1 -1
- package/dist/assets/{use-restore-focus-BxEGwIbk.js → use-restore-focus-DdS7Tl0F.js} +1 -1
- package/dist/assets/{useAddCell-DGstJK0S.js → useAddCell-Cjuqk2YR.js} +1 -1
- package/dist/assets/useAsyncData-qvoPYHtx.js +1 -0
- package/dist/assets/useBoolean-Dd2nWXFJ.js +1 -0
- package/dist/assets/useCellActionButton-MoWLvuGe.js +1 -0
- package/dist/assets/useDebounce-DwTQ_1Pu.js +1 -0
- package/dist/assets/{useDeepCompareMemoize-DFIVN9dn.js → useDeepCompareMemoize-B2DVK2De.js} +1 -1
- package/dist/assets/useDelayElapsed-BlsDT4TV.js +1 -0
- package/dist/assets/{useDeleteCell-DFBGlSQk.js → useDeleteCell-BC-MW7d1.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-DydxsE5d.js → useDependencyPanelTab-CZU7i-B3.js} +1 -1
- package/dist/assets/{useEvent-Dfcbgjhg.js → useEvent-BDjgaBtz.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-wcDq9hYT.js} +1 -1
- package/dist/assets/{useInterval--x3s1VpI.js → useInterval-DJdQmXnq.js} +1 -1
- package/dist/assets/useNotebookActions-wFxVdRXQ.js +1 -0
- package/dist/assets/{useRunCells-BFjFX3hz.js → useRunCells-ByaWxoIt.js} +1 -1
- package/dist/assets/{useSplitCell-Tqv5pPtL.js → useSplitCell-YJIystwv.js} +1 -1
- package/dist/assets/useStateForKey-CGMJQT8x.js +1 -0
- package/dist/assets/{useTheme-COhSHkCj.js → useTheme-BAA27lh7.js} +1 -1
- package/dist/assets/{utils-CaQYvazl.js → utils-DfElfBpz.js} +1 -1
- package/dist/assets/vega-component-BeyxAJad.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/{worker-DTQ7oH2A.js → worker-bSCjCvwz.js} +1 -1
- package/dist/assets/{write-secret-modal-DzKAInL2.js → write-secret-modal-BEx-EUlh.js} +1 -1
- package/dist/assets/{xychartDiagram-S5SC5T6Z-KbGzG6-u.js → xychartDiagram-S5SC5T6Z-CeRnBS5e.js} +1 -1
- package/dist/index.html +80 -78
- package/package.json +7 -7
- package/src/__tests__/host-imports.test.ts +1 -0
- package/src/components/app-config/ai-config.tsx +7 -2
- package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
- package/src/components/chat/acp/__tests__/state.test.ts +2 -2
- package/src/components/chat/acp/blocks.tsx +2 -2
- package/src/components/chat/chat-components.tsx +2 -0
- package/src/components/chat/chat-panel.tsx +9 -8
- package/src/components/chat/chat-utils.ts +4 -8
- package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
- package/src/components/data-table/__tests__/columns.test.tsx +105 -0
- package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
- package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
- package/src/components/data-table/__tests__/export-actions.test.tsx +105 -0
- package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
- package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
- package/src/components/data-table/cell-selection/feature.ts +2 -2
- package/src/components/data-table/cell-selection/types.ts +2 -2
- package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
- package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
- package/src/components/data-table/charts/charts.tsx +35 -25
- package/src/components/data-table/charts/storage.ts +16 -5
- package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
- package/src/components/data-table/column-summary/column-summary.tsx +2 -4
- package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
- package/src/components/data-table/columns.tsx +45 -46
- package/src/components/data-table/context-menu.tsx +36 -20
- package/src/components/data-table/data-table.tsx +7 -1
- package/src/components/data-table/date-popover.tsx +6 -4
- package/src/components/data-table/export-actions.tsx +76 -48
- package/src/components/data-table/row-viewer-panel/row-viewer.tsx +2 -2
- package/src/components/data-table/search-highlight.tsx +79 -0
- package/src/components/data-table/types.ts +1 -0
- package/src/components/data-table/url-detector.tsx +49 -9
- package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
- package/src/components/datasources/__tests__/column-preview.test.tsx +63 -2
- package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
- package/src/components/datasources/column-preview.tsx +8 -2
- package/src/components/datasources/datasources.tsx +47 -35
- package/src/components/debugger/debugger-code.tsx +5 -0
- package/src/components/dependency-graph/panels.tsx +2 -1
- package/src/components/editor/SortableCell.tsx +1 -3
- package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
- package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +24 -0
- package/src/components/editor/actions/pair-with-agent-modal.tsx +18 -15
- package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
- package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
- package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
- package/src/components/editor/cell/CreateCellButton.tsx +8 -10
- package/src/components/editor/cell/DeleteButton.tsx +1 -0
- package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
- package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
- package/src/components/editor/cell/code/cell-editor.tsx +1 -1
- package/src/components/editor/cell/code/language-toggle.tsx +8 -7
- package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
- package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
- package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
- package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
- package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +12 -18
- package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +58 -1
- package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
- package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
- package/src/components/editor/chrome/wrapper/storage.ts +1 -1
- 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 +12 -23
- package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
- package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
- package/src/components/editor/file-tree/file-explorer.tsx +10 -5
- package/src/components/editor/file-tree/file-viewer.tsx +4 -2
- package/src/components/editor/file-tree/use-file-search.ts +35 -21
- package/src/components/editor/notebook-banner.tsx +1 -0
- package/src/components/editor/output/MarimoTracebackOutput.tsx +2 -2
- package/src/components/editor/output/ansi-reduce.ts +6 -0
- package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
- package/src/components/editor/package-alert.tsx +6 -4
- package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
- package/src/components/home/components.tsx +17 -31
- package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
- package/src/components/icons/large-spinner.tsx +3 -14
- package/src/components/icons/spinner.tsx +1 -1
- package/src/components/mcp/mcp-status-indicator.tsx +1 -1
- package/src/components/pages/__tests__/home-page.test.tsx +116 -0
- package/src/components/pages/home-page.tsx +164 -91
- package/src/components/scratchpad/scratchpad.tsx +2 -2
- package/src/components/slides/reveal-component.tsx +4 -2
- package/src/components/slides/slide-cell-view.tsx +1 -1
- package/src/components/slides/slide-form.tsx +1 -0
- package/src/components/tracing/tracing.tsx +2 -2
- package/src/components/ui/alert-dialog.tsx +1 -1
- package/src/core/ai/context/providers/datasource.ts +6 -2
- package/src/core/attachments/state.ts +5 -8
- package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -0
- package/src/core/cells/__tests__/cell.test.ts +39 -0
- package/src/core/cells/__tests__/cells.test.ts +3 -1
- package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
- package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
- package/src/core/cells/cell.ts +14 -3
- package/src/core/cells/cells.ts +3 -1
- package/src/core/cells/pending-delete-service.ts +2 -6
- package/src/core/cells/session.ts +1 -1
- package/src/core/cells/types.ts +10 -1
- package/src/core/codemirror/__tests__/setup.test.ts +1 -1
- package/src/core/codemirror/keymaps/vim.ts +2 -6
- package/src/core/codemirror/language/languages/markdown.ts +1 -1
- package/src/core/codemirror/language/languages/python.ts +3 -3
- package/src/core/codemirror/language/panel/panel.tsx +1 -0
- 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/document/__tests__/draft.test.ts +322 -0
- package/src/core/document/__tests__/fixtures.ts +92 -0
- package/src/core/document/__tests__/lifecycle-scenarios.test.ts +61 -0
- package/src/core/document/__tests__/model.test.ts +158 -0
- package/src/core/document/__tests__/protocol-fixtures.ts +38 -0
- package/src/core/document/__tests__/recovery-scenarios.test.ts +230 -0
- package/src/core/document/__tests__/scenario-fixture.ts +42 -0
- package/src/core/document/__tests__/scenarios.test.ts +205 -0
- package/src/core/document/__tests__/state.test.ts +446 -0
- package/src/core/document/__tests__/submission.test.ts +201 -0
- package/src/core/document/__tests__/test-host.test.ts +92 -0
- package/src/core/document/__tests__/test-host.ts +247 -0
- package/src/core/document/__tests__/test-notebook.ts +139 -0
- package/src/core/document/__tests__/user.ts +61 -0
- package/src/core/document/draft.ts +472 -0
- package/src/core/document/index.ts +10 -0
- package/src/core/document/model.ts +53 -0
- package/src/core/document/state.ts +401 -0
- package/src/core/document/sync.ts +359 -0
- package/src/core/edit-app.tsx +2 -2
- package/src/core/host/AGENTS.md +12 -2
- package/src/core/host/__tests__/attachments.test.ts +56 -0
- package/src/core/host/__tests__/document.test.ts +126 -0
- package/src/core/host/__tests__/fixtures.ts +41 -0
- package/src/core/host/__tests__/notebook-client.test.ts +570 -0
- package/src/core/host/connection.ts +42 -0
- package/src/core/host/notebook-client.ts +297 -0
- package/src/core/host/resources/attachments.ts +22 -0
- package/src/core/host/resources/document.ts +101 -0
- package/src/core/hotkeys/actions.ts +2 -4
- package/src/core/islands/__tests__/bootstrap.test.ts +0 -26
- package/src/core/islands/bootstrap.ts +0 -1
- package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
- package/src/core/network/connection.ts +4 -2
- package/src/core/network/useConnectionNotice.ts +8 -2
- package/src/core/run-app.tsx +2 -2
- package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
- package/src/core/runtime/__tests__/runtime.test.ts +18 -17
- package/src/core/runtime/runtime.ts +26 -30
- package/src/core/state/__tests__/jotai.test.tsx +108 -0
- package/src/core/state/jotai.ts +1 -1
- package/src/core/static/files.ts +2 -0
- package/src/core/wasm/bridge.ts +2 -10
- package/src/core/wasm/store.ts +3 -3
- package/src/core/wasm/worker/bootstrap.ts +1 -1
- package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
- package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -29
- package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +223 -0
- package/src/core/websocket/__tests__/useNotebookSession.hook.test.tsx +606 -0
- package/src/core/websocket/connection-state.ts +60 -0
- package/src/core/websocket/state.ts +7 -0
- package/src/core/websocket/transports/__tests__/notebook.test.ts +502 -0
- package/src/core/websocket/transports/notebook.ts +213 -0
- package/src/core/websocket/types.ts +9 -39
- package/src/core/websocket/useNotebookClient.ts +89 -0
- package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +53 -233
- package/src/css/globals.css +5 -0
- package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
- package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
- package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
- package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
- package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
- package/src/hooks/__tests__/useScript.test.tsx +77 -0
- package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
- package/src/hooks/useAsyncData.ts +3 -3
- package/src/hooks/useDataSourceDiscovery.ts +6 -4
- package/src/hooks/useDebounce.ts +5 -8
- package/src/hooks/useDeepCompareMemoize.ts +1 -1
- package/src/hooks/useDelayElapsed.ts +14 -10
- package/src/hooks/useElapsedTime.ts +3 -3
- package/src/hooks/useInputHistory.ts +4 -7
- package/src/hooks/useInternalStateWithSync.ts +6 -6
- package/src/hooks/useResizeObserver.ts +4 -7
- package/src/hooks/useScript.ts +67 -70
- package/src/hooks/useStateForKey.ts +93 -0
- package/src/plugins/core/registerReactComponent.tsx +1 -1
- package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
- package/src/plugins/impl/DataTablePlugin.tsx +14 -15
- package/src/plugins/impl/DropdownPlugin.tsx +2 -6
- package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
- package/src/plugins/impl/FormPlugin.tsx +1 -1
- package/src/plugins/impl/RangeSliderPlugin.tsx +0 -1
- package/src/plugins/impl/RefreshPlugin.tsx +1 -0
- package/src/plugins/impl/SliderPlugin.tsx +6 -8
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +117 -1
- package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
- package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
- package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
- package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
- package/src/plugins/impl/anywidget/runtime.ts +21 -9
- package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
- package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
- package/src/plugins/impl/chat/chat-ui.tsx +12 -10
- 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/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
- package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
- package/src/plugins/impl/data-frames/DataFramePlugin.tsx +0 -1
- package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
- package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
- package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
- package/src/plugins/impl/panel/utils.ts +17 -0
- package/src/plugins/impl/vega/make-selectable.ts +4 -7
- package/src/plugins/layout/RoutesPlugin.tsx +1 -1
- package/src/stories/__tests__/editor-story.test.tsx +33 -0
- package/src/stories/button.stories.tsx +1 -0
- package/src/stories/editor.stories.tsx +24 -17
- package/src/stories/form-wrapper.stories.tsx +2 -2
- package/src/stories/select.stories.tsx +2 -2
- package/src/stories/switchable-multi-select.stories.tsx +1 -1
- package/src/utils/__tests__/arrays.test.ts +35 -0
- package/src/utils/__tests__/batch-requests.test.ts +3 -3
- 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/blob.ts +1 -1
- package/src/utils/numbers.ts +28 -17
- package/src/utils/objects.ts +3 -7
- package/src/utils/once.ts +19 -19
- package/src/utils/zod-utils.ts +1 -3
- package/dist/assets/CellStatus-nZpuDkm7.js +0 -1
- package/dist/assets/JsonOutput-BxB0NL8w.js +0 -11
- package/dist/assets/MarimoErrorOutput-BTy4MOzX.js +0 -10
- package/dist/assets/RunButton-CFQUNN5c.js +0 -1
- package/dist/assets/__vite-browser-external-CYO_P3S8.js +0 -1
- package/dist/assets/__vite-browser-external-FeDVGZU8.js +0 -1
- package/dist/assets/ai-model-dropdown-zm-2fXzf.js +0 -13
- package/dist/assets/any-language-editor-BLCAD3yx.js +0 -1
- package/dist/assets/arrays-BqUx5USx.js +0 -1
- package/dist/assets/cell-editor-CAj1UKxx.js +0 -21
- package/dist/assets/cell-link-1CynTVGQ.js +0 -1
- package/dist/assets/chat-panel-Ce2Xh7dM.js +0 -4
- package/dist/assets/chat-ui-DTy98rP3.js +0 -4
- package/dist/assets/circle-play-CYIb0qT6.js +0 -1
- package/dist/assets/code-visibility-B-_GvwR5.js +0 -1
- package/dist/assets/coerce-CpE6kUdX.js +0 -1
- package/dist/assets/column-preview-DU6dT80y.js +0 -1
- package/dist/assets/command-palette-BMqFKXVr.js +0 -1
- package/dist/assets/components-CXfO-F47.js +0 -1
- package/dist/assets/config-DX8Y2WbJ.js +0 -1
- package/dist/assets/config-VPSq73n8.js +0 -1
- package/dist/assets/edit-page-DlfCLUaq.js +0 -9
- package/dist/assets/feedback-button-DlqZkJwz.js +0 -1
- package/dist/assets/file-explorer-panel-Bo9eN3rW.js +0 -1
- package/dist/assets/home-page-KrhQ55Vg.js +0 -4
- package/dist/assets/index-6b4mf8Ai.css +0 -2
- package/dist/assets/index-B3fcrmuf.js +0 -30
- package/dist/assets/kiosk-mode-BZECfg9k.js +0 -1
- package/dist/assets/layout-DOpdUwym.js +0 -9
- package/dist/assets/linear-0eij_aAG.js +0 -1
- package/dist/assets/link-NuFVR7G9.js +0 -1
- package/dist/assets/links-DkkL9DVa.js +0 -1
- package/dist/assets/numbers-Mk3-CcoU.js +0 -1
- package/dist/assets/packages-panel-ClxOAEGy.js +0 -1
- package/dist/assets/panels-DfCmzLtT.js +0 -4
- package/dist/assets/precisionRound-Bq_nmf5u.js +0 -1
- package/dist/assets/process-output-DNFKMODr.js +0 -1
- package/dist/assets/react-vega-C3rYGtP6.js +0 -189
- package/dist/assets/requests-CgSY1X4a.js +0 -1
- package/dist/assets/run-page-Cxu1yV8Q.js +0 -1
- package/dist/assets/scratchpad-panel-By-ybhjg.js +0 -1
- package/dist/assets/session-panel-BmW6PH4z.js +0 -1
- package/dist/assets/state-BAJYRhmO.js +0 -1
- package/dist/assets/state-z3dlofZB.js +0 -3
- package/dist/assets/tooltip-mT3CGest.js +0 -61
- package/dist/assets/tracing-BARlJ_Dk.js +0 -1
- package/dist/assets/tracing-panel-dgnr2fIN.js +0 -2
- package/dist/assets/useAsyncData-C7aJJz0Y.js +0 -1
- package/dist/assets/useBoolean-BDD6_xQQ.js +0 -1
- package/dist/assets/useCellActionButton-DY9RlJTb.js +0 -1
- package/dist/assets/useDebounce-B_ccBZTp.js +0 -1
- package/dist/assets/useNotebookActions-DfvB7RD_.js +0 -1
- package/dist/assets/vega-component-CnadTgoE.js +0 -1
- package/dist/assets/vega-loader.browser-CLRkNgm7.js +0 -6
- package/dist/assets/ws-BXWY541V.js +0 -22
- package/src/core/host/__tests__/protocol.test.ts +0 -214
- package/src/core/host/protocol.ts +0 -1943
- package/src/core/state/__tests__/jotai.test.ts +0 -25
- package/src/core/websocket/__tests__/useMarimoKernelConnection.hook.test.tsx +0 -789
- package/src/core/websocket/__tests__/useWebSocket.test.ts +0 -49
- package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
- package/src/core/websocket/transports/__tests__/sse.test.ts +0 -338
- package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
- package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
- package/src/core/websocket/transports/basic.ts +0 -102
- package/src/core/websocket/transports/sse.ts +0 -308
- package/src/core/websocket/transports/transport.ts +0 -61
- package/src/core/websocket/transports/ws.ts +0 -98
- package/src/core/websocket/useWebSocket.tsx +0 -88
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { Logger } from "@/utils/Logger";
|
|
4
|
+
import {
|
|
5
|
+
createNotebookReducer,
|
|
6
|
+
type NotebookEvent,
|
|
7
|
+
NotebookEvents,
|
|
8
|
+
type NotebookTransport,
|
|
9
|
+
type Result,
|
|
10
|
+
StreamStopped,
|
|
11
|
+
} from "@marimo-team/client";
|
|
12
|
+
import type {
|
|
13
|
+
Attachment,
|
|
14
|
+
Change,
|
|
15
|
+
EditResponseOutput,
|
|
16
|
+
NotebookId,
|
|
17
|
+
NotebookOutput,
|
|
18
|
+
} from "@marimo-team/client/protocol";
|
|
19
|
+
import { type Atom, atom, type createStore } from "jotai/vanilla";
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import {
|
|
22
|
+
ConnectionClosedReason,
|
|
23
|
+
ConnectionState,
|
|
24
|
+
type ConnectionStatus,
|
|
25
|
+
} from "./connection";
|
|
26
|
+
import { attachments } from "./resources/attachments";
|
|
27
|
+
import { type Document, document } from "./resources/document";
|
|
28
|
+
|
|
29
|
+
/** The kinds the editor reads, with marimo's own notifications. */
|
|
30
|
+
const TYPES = ["notebook", "cell", "document", "attachment", "marimo"] as const;
|
|
31
|
+
|
|
32
|
+
type EditorEvent = NotebookEvent<(typeof TYPES)[number]>;
|
|
33
|
+
|
|
34
|
+
/** A native marimo notification, passed through without interpretation. */
|
|
35
|
+
const MarimoMessage = z.object({ marimo: z.unknown() });
|
|
36
|
+
|
|
37
|
+
/** A connection to a notebook on a marimo host. */
|
|
38
|
+
export interface NotebookClient {
|
|
39
|
+
/** Client connection status, independent of application session readiness. */
|
|
40
|
+
readonly connectionAtom: Atom<ConnectionStatus>;
|
|
41
|
+
/** Identity and capabilities, available while the stream is connected and ready. */
|
|
42
|
+
readonly notebookAtom: Atom<NotebookOutput | null>;
|
|
43
|
+
/**
|
|
44
|
+
* The most recently confirmed document.
|
|
45
|
+
*
|
|
46
|
+
* Null until a snapshot is complete, after reset or removal, and on
|
|
47
|
+
* connections without host document events. A temporary disconnection
|
|
48
|
+
* retains the last document. Partial updates are not exposed.
|
|
49
|
+
*/
|
|
50
|
+
readonly documentAtom: Atom<Document | null>;
|
|
51
|
+
/** Apply an edit to the notebook captured by the caller, even across a reset. */
|
|
52
|
+
edit(
|
|
53
|
+
notebookId: NotebookId,
|
|
54
|
+
changes: readonly Change[],
|
|
55
|
+
options: { readonly idempotencyKey: string; readonly signal: AbortSignal },
|
|
56
|
+
): Promise<Result<EditResponseOutput>>;
|
|
57
|
+
/** Live attachments, empty until the stream is ready or while disconnected. */
|
|
58
|
+
readonly attachmentsAtom: Atom<readonly Attachment[]>;
|
|
59
|
+
/** Attempt to restore a closed connection after checking host health. */
|
|
60
|
+
reconnect(): Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The transport and application callbacks needed to follow a notebook. */
|
|
64
|
+
export interface NotebookConnection {
|
|
65
|
+
/** Opens one connection to the notebook's stream. */
|
|
66
|
+
transport: NotebookTransport;
|
|
67
|
+
/** Abort to close the stream and clear the client's state. */
|
|
68
|
+
signal: AbortSignal;
|
|
69
|
+
/** Check whether the host is reachable before an explicit reconnect. */
|
|
70
|
+
checkHealth: () => Promise<boolean>;
|
|
71
|
+
/** Handle a native marimo notification. Its payload has not been validated. */
|
|
72
|
+
onNotification: (notification: unknown) => void;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const disconnected = {
|
|
76
|
+
state: ConnectionState.CLOSED,
|
|
77
|
+
code: ConnectionClosedReason.KERNEL_DISCONNECTED,
|
|
78
|
+
reason: "kernel not found",
|
|
79
|
+
} satisfies ConnectionStatus;
|
|
80
|
+
|
|
81
|
+
const malformed = {
|
|
82
|
+
...disconnected,
|
|
83
|
+
reason: "Failed to decode notebook message",
|
|
84
|
+
} satisfies ConnectionStatus;
|
|
85
|
+
|
|
86
|
+
function isStatus(value: unknown): value is ConnectionStatus {
|
|
87
|
+
return typeof value === "object" && value !== null && "state" in value;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The status to show after the stream stopped.
|
|
92
|
+
*
|
|
93
|
+
* A transport says which status its stop means. An event the protocol
|
|
94
|
+
* rejects is malformed. Anything else means the host was not reached.
|
|
95
|
+
*/
|
|
96
|
+
function statusAfter(error: unknown): ConnectionStatus {
|
|
97
|
+
if (error instanceof StreamStopped && isStatus(error.cause)) {
|
|
98
|
+
return error.cause;
|
|
99
|
+
}
|
|
100
|
+
if (error instanceof StreamStopped && error.cause instanceof z.ZodError) {
|
|
101
|
+
return malformed;
|
|
102
|
+
}
|
|
103
|
+
return disconnected;
|
|
104
|
+
}
|
|
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
|
+
) {
|
|
114
|
+
// `useNotebookSession` copies this into the editor's shared `connectionAtom`,
|
|
115
|
+
// which gates features until MO-8051 derives availability separately.
|
|
116
|
+
const connectionAtom = atom<ConnectionStatus>({
|
|
117
|
+
state: ConnectionState.NOT_STARTED,
|
|
118
|
+
});
|
|
119
|
+
// One atom holds the whole stream, which resource atoms derive from.
|
|
120
|
+
const reducer = createNotebookReducer({ document, attachments });
|
|
121
|
+
const streamAtom = atom(reducer.initial);
|
|
122
|
+
// A resumed stream is live, but presence waits for this connection's ready.
|
|
123
|
+
const readyAtom = atom(false);
|
|
124
|
+
const presentAtom = atom(
|
|
125
|
+
(get) =>
|
|
126
|
+
get(readyAtom) && get(connectionAtom).state === ConnectionState.OPEN,
|
|
127
|
+
);
|
|
128
|
+
const notebookAtom = atom((get) =>
|
|
129
|
+
get(presentAtom) ? get(streamAtom).notebook : null,
|
|
130
|
+
);
|
|
131
|
+
const documentAtom = atom(
|
|
132
|
+
(get) => get(streamAtom).resources.document.current,
|
|
133
|
+
);
|
|
134
|
+
const attachmentsAtom = atom((get) =>
|
|
135
|
+
get(presentAtom)
|
|
136
|
+
? get(streamAtom).resources.attachments
|
|
137
|
+
: attachments.initial,
|
|
138
|
+
);
|
|
139
|
+
let reconnect: (() => Promise<void>) | undefined;
|
|
140
|
+
const client: NotebookClient = {
|
|
141
|
+
connectionAtom,
|
|
142
|
+
notebookAtom,
|
|
143
|
+
documentAtom,
|
|
144
|
+
edit: async (notebookId, changes, options) => {
|
|
145
|
+
options.signal.throwIfAborted();
|
|
146
|
+
const currentId = store.get(streamAtom).notebookId;
|
|
147
|
+
if (currentId !== null && currentId !== notebookId) {
|
|
148
|
+
throw new Error("The notebook changed before the edit was sent");
|
|
149
|
+
}
|
|
150
|
+
if (edit === undefined) {
|
|
151
|
+
return {
|
|
152
|
+
ok: false,
|
|
153
|
+
status: 501,
|
|
154
|
+
problem: {
|
|
155
|
+
type: "tag:marimo.io,2026:host/unsupported-operation",
|
|
156
|
+
title: "Document editing is unavailable on this connection",
|
|
157
|
+
status: 501,
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return edit(notebookId, changes, options);
|
|
162
|
+
},
|
|
163
|
+
attachmentsAtom,
|
|
164
|
+
reconnect: async () => reconnect?.(),
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
function setStatus(status: ConnectionStatus) {
|
|
168
|
+
store.set(readyAtom, false);
|
|
169
|
+
store.set(connectionAtom, status);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function connect({
|
|
173
|
+
transport,
|
|
174
|
+
signal,
|
|
175
|
+
checkHealth,
|
|
176
|
+
onNotification,
|
|
177
|
+
}: NotebookConnection) {
|
|
178
|
+
if (signal.aborted) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
let events: NotebookEvents<(typeof TYPES)[number]> | undefined;
|
|
182
|
+
let checkingHealth = false;
|
|
183
|
+
// Whether the replica still holds the state the last cursor describes.
|
|
184
|
+
let resumable = true;
|
|
185
|
+
|
|
186
|
+
/** Mirror one stream into the atoms until it closes. */
|
|
187
|
+
function follow(cursor?: string) {
|
|
188
|
+
if (cursor === undefined) {
|
|
189
|
+
store.set(streamAtom, reducer.initial);
|
|
190
|
+
}
|
|
191
|
+
resumable = true;
|
|
192
|
+
const current = new NotebookEvents(transport, {
|
|
193
|
+
types: TYPES,
|
|
194
|
+
cursor,
|
|
195
|
+
signal,
|
|
196
|
+
// TODO(MO-8051): Gate editing and running on the notebook's operations
|
|
197
|
+
// and request results instead. The stream's status would then only
|
|
198
|
+
// mark the view as stale.
|
|
199
|
+
onStatus: (status) => {
|
|
200
|
+
if (status === "connecting") {
|
|
201
|
+
setStatus({ state: ConnectionState.CONNECTING });
|
|
202
|
+
} else if (status === "connected") {
|
|
203
|
+
setStatus({ state: ConnectionState.OPEN });
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
current.subscribe(
|
|
208
|
+
(event) => apply(current, event),
|
|
209
|
+
(error) => {
|
|
210
|
+
if (!signal.aborted && current === events) {
|
|
211
|
+
Logger.warn("Notebook stream stopped", error);
|
|
212
|
+
const status = statusAfter(error);
|
|
213
|
+
if (status === malformed) {
|
|
214
|
+
stopMalformed(current);
|
|
215
|
+
} else {
|
|
216
|
+
setStatus(status);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
);
|
|
221
|
+
events = current;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function stopMalformed(source: NotebookEvents<string>) {
|
|
225
|
+
source.close();
|
|
226
|
+
// The cursor can already include an event the replica rejected.
|
|
227
|
+
resumable = false;
|
|
228
|
+
store.set(streamAtom, reducer.initial);
|
|
229
|
+
setStatus(malformed);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function apply(source: NotebookEvents<string>, event: EditorEvent) {
|
|
233
|
+
if (event.event === "marimo") {
|
|
234
|
+
const message = MarimoMessage.safeParse(event.data);
|
|
235
|
+
if (message.success) {
|
|
236
|
+
onNotification(message.data.marimo);
|
|
237
|
+
} else {
|
|
238
|
+
Logger.error("Failed to decode notebook message", event);
|
|
239
|
+
stopMalformed(source);
|
|
240
|
+
}
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
store.set(streamAtom, reducer.reduce(store.get(streamAtom), event));
|
|
245
|
+
} catch (error) {
|
|
246
|
+
// An event the replica cannot apply, such as another notebook's.
|
|
247
|
+
Logger.error("Failed to apply notebook event", error);
|
|
248
|
+
stopMalformed(source);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (event.event === "ready" || event.event === "reset") {
|
|
252
|
+
store.set(readyAtom, event.event === "ready");
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
reconnect = async () => {
|
|
257
|
+
if (signal.aborted || checkingHealth || events?.status !== "closed") {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
checkingHealth = true;
|
|
261
|
+
setStatus({ state: ConnectionState.CONNECTING });
|
|
262
|
+
try {
|
|
263
|
+
const healthy = await checkHealth();
|
|
264
|
+
if (signal.aborted) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (!healthy) {
|
|
268
|
+
setStatus(disconnected);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
follow(resumable ? (events.cursor ?? undefined) : undefined);
|
|
272
|
+
} catch (error) {
|
|
273
|
+
if (!signal.aborted) {
|
|
274
|
+
Logger.error("Notebook health check failed", error);
|
|
275
|
+
setStatus(disconnected);
|
|
276
|
+
}
|
|
277
|
+
} finally {
|
|
278
|
+
checkingHealth = false;
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
signal.addEventListener(
|
|
283
|
+
"abort",
|
|
284
|
+
() => {
|
|
285
|
+
reconnect = undefined;
|
|
286
|
+
store.set(streamAtom, reducer.initial);
|
|
287
|
+
setStatus({ state: ConnectionState.NOT_STARTED });
|
|
288
|
+
},
|
|
289
|
+
{ once: true },
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
setStatus({ state: ConnectionState.CONNECTING });
|
|
293
|
+
follow();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return { client, connect };
|
|
297
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { Attachment } from "@marimo-team/client/protocol";
|
|
4
|
+
import type { NotebookResource } from "@marimo-team/client";
|
|
5
|
+
|
|
6
|
+
/** Attachment records retained with the stream's resume cursor. */
|
|
7
|
+
export const attachments: NotebookResource<
|
|
8
|
+
readonly Attachment[],
|
|
9
|
+
"attachment"
|
|
10
|
+
> = {
|
|
11
|
+
initial: [],
|
|
12
|
+
on: {
|
|
13
|
+
attachment: (state, { data: { attachment } }) =>
|
|
14
|
+
state.some((item) => item.id === attachment.id)
|
|
15
|
+
? state.map((item) => (item.id === attachment.id ? attachment : item))
|
|
16
|
+
: [...state, attachment],
|
|
17
|
+
"attachment.removed": (state, { data }) =>
|
|
18
|
+
state.some((item) => item.id === data.id)
|
|
19
|
+
? state.filter((item) => item.id !== data.id)
|
|
20
|
+
: state,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type * as Protocol from "@marimo-team/client/protocol";
|
|
4
|
+
import type { NotebookResource } from "@marimo-team/client";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The notebook's content at one revision.
|
|
8
|
+
*
|
|
9
|
+
* The revision record carries the cell order, app config, etags, and
|
|
10
|
+
* version. Each cell in its order has a record in `cells`.
|
|
11
|
+
*/
|
|
12
|
+
export interface Document {
|
|
13
|
+
readonly revision: Protocol.Document;
|
|
14
|
+
readonly cells: ReadonlyMap<Protocol.CellId, Protocol.Cell>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** The records of a revision still being received. */
|
|
18
|
+
interface PendingDocument {
|
|
19
|
+
/** Null until the `document` event that closes the group arrives. */
|
|
20
|
+
readonly revision: Protocol.Document | null;
|
|
21
|
+
readonly cells: ReadonlyMap<Protocol.CellId, Protocol.Cell>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The confirmed document and the revision being assembled after it.
|
|
26
|
+
*
|
|
27
|
+
* A snapshot's records are pending until `ready`. A live revision is pending
|
|
28
|
+
* from its first cell until its `document` event.
|
|
29
|
+
*/
|
|
30
|
+
interface DocumentState {
|
|
31
|
+
readonly current: Document | null;
|
|
32
|
+
readonly pending: PendingDocument | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function pendingOf(state: DocumentState): PendingDocument {
|
|
36
|
+
return (
|
|
37
|
+
state.pending ?? {
|
|
38
|
+
revision: state.current?.revision ?? null,
|
|
39
|
+
cells: state.current?.cells ?? new Map(),
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Confirm a revision whose records are complete. */
|
|
45
|
+
function publish(
|
|
46
|
+
revision: Protocol.Document,
|
|
47
|
+
cells: ReadonlyMap<Protocol.CellId, Protocol.Cell>,
|
|
48
|
+
): DocumentState {
|
|
49
|
+
const order = new Set(revision.order);
|
|
50
|
+
if (
|
|
51
|
+
order.size !== revision.order.length ||
|
|
52
|
+
order.size !== cells.size ||
|
|
53
|
+
[...order].some((id) => !cells.has(id)) ||
|
|
54
|
+
[...cells.values()].some((cell) => cell.version > revision.version)
|
|
55
|
+
) {
|
|
56
|
+
throw new Error("Document records do not match its revision and order");
|
|
57
|
+
}
|
|
58
|
+
return { current: { revision, cells }, pending: null };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const document: NotebookResource<DocumentState, "cell" | "document"> = {
|
|
62
|
+
initial: { current: null, pending: null },
|
|
63
|
+
on: {
|
|
64
|
+
cell: (state, { data }) => {
|
|
65
|
+
const pending = pendingOf(state);
|
|
66
|
+
return {
|
|
67
|
+
...state,
|
|
68
|
+
pending: {
|
|
69
|
+
revision: pending.revision,
|
|
70
|
+
cells: new Map(pending.cells).set(data.cell.id, data.cell),
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
"cell.removed": (state, { data }) => {
|
|
75
|
+
const pending = pendingOf(state);
|
|
76
|
+
if (!pending.cells.has(data.id)) {
|
|
77
|
+
return state;
|
|
78
|
+
}
|
|
79
|
+
const cells = new Map(pending.cells);
|
|
80
|
+
cells.delete(data.id);
|
|
81
|
+
return { ...state, pending: { ...pending, cells } };
|
|
82
|
+
},
|
|
83
|
+
document: (state, { data }, { phase }) => {
|
|
84
|
+
const { cells } = pendingOf(state);
|
|
85
|
+
return phase === "snapshot"
|
|
86
|
+
? { ...state, pending: { revision: data.document, cells } }
|
|
87
|
+
: publish(data.document, cells);
|
|
88
|
+
},
|
|
89
|
+
ready: (state, _event, { phase }) => {
|
|
90
|
+
if (state.pending === null) {
|
|
91
|
+
return state;
|
|
92
|
+
}
|
|
93
|
+
if (phase === "live") {
|
|
94
|
+
throw new Error("Stream ended with an incomplete document update");
|
|
95
|
+
}
|
|
96
|
+
return state.pending.revision === null
|
|
97
|
+
? { current: null, pending: null }
|
|
98
|
+
: publish(state.pending.revision, state.pending.cells);
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { atom, useAtomValue, useSetAtom } from "jotai";
|
|
3
3
|
import useEvent from "react-use-event-hook";
|
|
4
4
|
import type { HotkeyAction } from "@/core/hotkeys/hotkeys";
|
|
5
|
+
import { Objects } from "@/utils/objects";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Map of registered keyboard shortcuts and their callbacks.
|
|
@@ -29,10 +30,7 @@ export function useSetRegisteredAction() {
|
|
|
29
30
|
},
|
|
30
31
|
),
|
|
31
32
|
unregisterAction: useEvent((shortcut: HotkeyAction) => {
|
|
32
|
-
set((actions) =>
|
|
33
|
-
const { [shortcut]: unused, ...rest } = actions;
|
|
34
|
-
return rest;
|
|
35
|
-
});
|
|
33
|
+
set((actions) => Objects.omit(actions, [shortcut]));
|
|
36
34
|
}),
|
|
37
35
|
};
|
|
38
36
|
}
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
import { createStore } from "jotai";
|
|
4
4
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
import { RuntimeState } from "@/core/kernel/RuntimeState";
|
|
6
|
-
import { attachmentsAtom } from "@/core/attachments/state";
|
|
7
|
-
import { Logger } from "@/utils/Logger";
|
|
8
6
|
import { initializeIslands } from "../bootstrap";
|
|
9
7
|
import type { IslandsPyodideBridge } from "../bridge";
|
|
10
8
|
import { islandsHydratedAtom, islandsPendingInitialRunsAtom } from "../state";
|
|
@@ -50,30 +48,6 @@ describe("initializeIslands", () => {
|
|
|
50
48
|
expect(store.get(islandsHydratedAtom)).toBe(false);
|
|
51
49
|
store.set(islandsPendingInitialRunsAtom, new Set([1, 2]));
|
|
52
50
|
|
|
53
|
-
const errors = vi.spyOn(Logger, "error");
|
|
54
|
-
consumeMessage?.({
|
|
55
|
-
sessionGeneration: 1,
|
|
56
|
-
message: JSON.stringify({
|
|
57
|
-
op: "attachments",
|
|
58
|
-
data: {
|
|
59
|
-
op: "attachments",
|
|
60
|
-
attachments: [
|
|
61
|
-
{
|
|
62
|
-
id: "a1",
|
|
63
|
-
kind: "agent",
|
|
64
|
-
name: "Pi",
|
|
65
|
-
since: "2026-10-14T00:00:00Z",
|
|
66
|
-
notebook_id: "nb-test",
|
|
67
|
-
identity: "pi-thread",
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
|
-
},
|
|
71
|
-
}) as IslandsKernelMessage["message"],
|
|
72
|
-
});
|
|
73
|
-
expect(errors).not.toHaveBeenCalled();
|
|
74
|
-
expect(store.get(attachmentsAtom)).toEqual([]);
|
|
75
|
-
expect(store.get(islandsPendingInitialRunsAtom)).toEqual(new Set([1, 2]));
|
|
76
|
-
|
|
77
51
|
consumeMessage?.(completedRun(1));
|
|
78
52
|
expect(store.get(islandsHydratedAtom)).toBe(false);
|
|
79
53
|
|
|
@@ -25,7 +25,7 @@ describe("RuntimeState", () => {
|
|
|
25
25
|
|
|
26
26
|
beforeEach(() => {
|
|
27
27
|
vi.clearAllMocks();
|
|
28
|
-
mockSendComponentValues = vi.fn((
|
|
28
|
+
mockSendComponentValues = vi.fn(() => Promise.resolve(null));
|
|
29
29
|
uiElementRegistry = UIElementRegistry.INSTANCE;
|
|
30
30
|
uiElementRegistry.entries.clear();
|
|
31
31
|
|
|
@@ -7,8 +7,10 @@ import { waitFor } from "../state/jotai";
|
|
|
7
7
|
import { type ConnectionStatus, ConnectionState } from "../websocket/types";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Shared application state, not the actual event-stream status.
|
|
11
|
+
*
|
|
12
|
+
* TODO(MO-8051) Expose read-only connection state and derive UI availability
|
|
13
|
+
* separately.
|
|
12
14
|
*/
|
|
13
15
|
export const connectionAtom = atom<ConnectionStatus>({
|
|
14
16
|
state: ConnectionState.NOT_STARTED,
|
|
@@ -17,11 +17,17 @@ export function useConnectionNotice(
|
|
|
17
17
|
const notice = useAtomValue(connectionNoticeAtom);
|
|
18
18
|
const connected = useAtomValue(isConnectedAtom);
|
|
19
19
|
const elapsed = useDelayElapsed(notice?.pending ? delayMs : 0);
|
|
20
|
-
const [previous, setPrevious] = useState<ConnectionNotice | null>(
|
|
20
|
+
const [previous, setPrevious] = useState<ConnectionNotice | null>(
|
|
21
|
+
elapsed ? notice : null,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const visibleNotice = elapsed ? notice : null;
|
|
25
|
+
if ((notice || !connected) && previous !== visibleNotice) {
|
|
26
|
+
setPrevious(visibleNotice);
|
|
27
|
+
}
|
|
21
28
|
|
|
22
29
|
useEffect(() => {
|
|
23
30
|
if (notice || !connected) {
|
|
24
|
-
setPrevious(elapsed ? notice : null);
|
|
25
31
|
return;
|
|
26
32
|
}
|
|
27
33
|
const timeout = setTimeout(() => setPrevious(null), 1500);
|
package/src/core/run-app.tsx
CHANGED
|
@@ -22,7 +22,7 @@ import { getSessionId } from "./kernel/session";
|
|
|
22
22
|
import { connectionAtom } from "./network/connection";
|
|
23
23
|
import { useRequestClient } from "./network/requests";
|
|
24
24
|
import { isAppConnecting } from "./websocket/connection-utils";
|
|
25
|
-
import {
|
|
25
|
+
import { useNotebookSession } from "./websocket/useNotebookSession";
|
|
26
26
|
|
|
27
27
|
interface AppProps {
|
|
28
28
|
appConfig: AppConfig;
|
|
@@ -51,7 +51,7 @@ export const RunApp: React.FC<AppProps> = ({ appConfig, hideHeader }) => {
|
|
|
51
51
|
};
|
|
52
52
|
}, []);
|
|
53
53
|
|
|
54
|
-
const { connection, reconnect } =
|
|
54
|
+
const { connection, reconnect } = useNotebookSession({
|
|
55
55
|
autoInstantiate: true,
|
|
56
56
|
setCells: setCells,
|
|
57
57
|
sessionId: getSessionId(),
|
|
@@ -13,7 +13,9 @@ describe("RuntimeManager.getWsURL", () => {
|
|
|
13
13
|
});
|
|
14
14
|
const sessionId = "1234" as SessionId;
|
|
15
15
|
const url = runtime.getWsURL(sessionId);
|
|
16
|
-
expect(url.toString()).toBe(
|
|
16
|
+
expect(url.toString()).toBe(
|
|
17
|
+
"wss://marimo.app/ws?query_params=&session_id=1234",
|
|
18
|
+
);
|
|
17
19
|
expect(url.searchParams.get(KnownQueryParams.sessionId)).toBe(sessionId);
|
|
18
20
|
});
|
|
19
21
|
|
|
@@ -24,7 +26,9 @@ describe("RuntimeManager.getWsURL", () => {
|
|
|
24
26
|
});
|
|
25
27
|
const sessionId = "1234" as SessionId;
|
|
26
28
|
const url = runtime.getWsURL(sessionId);
|
|
27
|
-
expect(url.toString()).toBe(
|
|
29
|
+
expect(url.toString()).toBe(
|
|
30
|
+
"ws://marimo.app/ws?query_params=&session_id=1234",
|
|
31
|
+
);
|
|
28
32
|
expect(url.searchParams.get(KnownQueryParams.sessionId)).toBe(sessionId);
|
|
29
33
|
});
|
|
30
34
|
|
|
@@ -35,7 +39,9 @@ describe("RuntimeManager.getWsURL", () => {
|
|
|
35
39
|
});
|
|
36
40
|
const sessionId = "1234" as SessionId;
|
|
37
41
|
const url = runtime.getWsURL(sessionId);
|
|
38
|
-
expect(url.toString()).toBe(
|
|
42
|
+
expect(url.toString()).toBe(
|
|
43
|
+
"ws://marimo.app/nested/ws?query_params=&session_id=1234",
|
|
44
|
+
);
|
|
39
45
|
expect(url.searchParams.get(KnownQueryParams.sessionId)).toBe(sessionId);
|
|
40
46
|
});
|
|
41
47
|
|
|
@@ -48,7 +54,7 @@ describe("RuntimeManager.getWsURL", () => {
|
|
|
48
54
|
const sessionId = "1234" as SessionId;
|
|
49
55
|
const url = runtime.getWsURL(sessionId);
|
|
50
56
|
expect(url.toString()).toBe(
|
|
51
|
-
"ws://marimo.app/nested/ws?foo=bar&file=test.py&session_id=1234",
|
|
57
|
+
"ws://marimo.app/nested/ws?foo=bar&file=test.py&query_params=foo%3Dbar%26file%3Dtest.py&session_id=1234",
|
|
52
58
|
);
|
|
53
59
|
expect(url.searchParams.get(KnownQueryParams.sessionId)).toBe(sessionId);
|
|
54
60
|
});
|
|
@@ -62,11 +68,75 @@ describe("RuntimeManager.getWsURL", () => {
|
|
|
62
68
|
const sessionId = "1234" as SessionId;
|
|
63
69
|
const url = runtime.getWsURL(sessionId);
|
|
64
70
|
expect(url.toString()).toBe(
|
|
65
|
-
"ws://marimo.app/ws?file=test.py&
|
|
71
|
+
"ws://marimo.app/ws?file=test.py&query_params=file%3Dtest.py%26search%3Dtest%26other%3Dlast&session_id=1234",
|
|
66
72
|
);
|
|
67
73
|
expect(url.searchParams.get("file")).toBe("test.py");
|
|
68
|
-
expect(url.searchParams.get("
|
|
69
|
-
|
|
74
|
+
expect(url.searchParams.get("query_params")).toBe(
|
|
75
|
+
"file=test.py&search=test&other=last",
|
|
76
|
+
);
|
|
70
77
|
expect(url.searchParams.get(KnownQueryParams.sessionId)).toBe(sessionId);
|
|
71
78
|
});
|
|
72
79
|
});
|
|
80
|
+
|
|
81
|
+
describe.each(["getWsURL", "getSseURL"] as const)(
|
|
82
|
+
"RuntimeManager.%s",
|
|
83
|
+
(method) => {
|
|
84
|
+
it("preserves runtime parameters and repeated page values", () => {
|
|
85
|
+
window.history.pushState({}, "", "/?tag=page-one&tag=page-two");
|
|
86
|
+
const runtime = new RuntimeManager(
|
|
87
|
+
{
|
|
88
|
+
url: `${window.location.origin}/nested?proxy_token=secret&tag=base&base=one&base=two`,
|
|
89
|
+
lazy: true,
|
|
90
|
+
},
|
|
91
|
+
true,
|
|
92
|
+
);
|
|
93
|
+
const url = runtime[method]("session" as SessionId);
|
|
94
|
+
expect([...url.searchParams]).toEqual([
|
|
95
|
+
["proxy_token", "secret"],
|
|
96
|
+
["tag", "base"],
|
|
97
|
+
["base", "one"],
|
|
98
|
+
["base", "two"],
|
|
99
|
+
[
|
|
100
|
+
"query_params",
|
|
101
|
+
"proxy_token=secret&base=one&base=two&tag=page-one&tag=page-two",
|
|
102
|
+
],
|
|
103
|
+
["session_id", "session"],
|
|
104
|
+
]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("keeps notebook parameters out of the stream options", () => {
|
|
108
|
+
const notebookParams = new URLSearchParams([
|
|
109
|
+
["types", "scatter"],
|
|
110
|
+
["cursor", "page-2"],
|
|
111
|
+
["run_id", "experiment"],
|
|
112
|
+
["query_params", "nested=value&other=1"],
|
|
113
|
+
["tag", "one"],
|
|
114
|
+
["tag", "two"],
|
|
115
|
+
["empty", ""],
|
|
116
|
+
["text", "a+b & café"],
|
|
117
|
+
["file", "test.py"],
|
|
118
|
+
["kiosk", "true"],
|
|
119
|
+
]);
|
|
120
|
+
window.history.pushState(
|
|
121
|
+
{},
|
|
122
|
+
"",
|
|
123
|
+
`/?${notebookParams}&session_id=page-session`,
|
|
124
|
+
);
|
|
125
|
+
const runtime = new RuntimeManager(
|
|
126
|
+
{ url: window.location.origin, lazy: true },
|
|
127
|
+
true,
|
|
128
|
+
);
|
|
129
|
+
const url = runtime[method]("actual-session" as SessionId);
|
|
130
|
+
|
|
131
|
+
expect(Object.fromEntries(url.searchParams)).toEqual({
|
|
132
|
+
file: "test.py",
|
|
133
|
+
kiosk: "true",
|
|
134
|
+
session_id: "actual-session",
|
|
135
|
+
query_params: notebookParams.toString(),
|
|
136
|
+
});
|
|
137
|
+
expect([
|
|
138
|
+
...new URLSearchParams(url.searchParams.get("query_params") ?? ""),
|
|
139
|
+
]).toEqual([...notebookParams]);
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
);
|