@marimo-team/frontend 0.25.1-dev90 → 0.25.2-dev0
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/{ConnectedDataExplorerComponent-Bdak5-o8.js → ConnectedDataExplorerComponent-DJ5jwKfp.js} +1 -1
- package/dist/assets/{ErrorBoundary-DrliQ_7g.js → ErrorBoundary-BeE1cdgM.js} +2 -2
- package/dist/assets/{ImageComparisonComponent-DLxkuRy3.js → ImageComparisonComponent-pCLPT6RA.js} +1 -1
- package/dist/assets/LazyAnyLanguageCodeMirror-B_IYTCX5.js +2 -0
- package/dist/assets/{RunButton-qknEPbRR.js → RunButton-C2kFUhyO.js} +10 -10
- package/dist/assets/{abnfDiagram-VCTEODGH-dm832Yw9.js → abnfDiagram-VCTEODGH-CSPJSGaM.js} +1 -1
- package/dist/assets/add-cell-with-ai-BFgdVPaN.js +76 -0
- package/dist/assets/{agent-panel-CJva58SL.js → agent-panel-Dh7yWh9P.js} +5 -5
- package/dist/assets/ai-model-dropdown-DJ2elMLM.js +5 -0
- package/dist/assets/{any-language-editor-B7QvjaYO.js → any-language-editor-Cj_HzlZY.js} +1 -1
- package/dist/assets/app-config-button-DBCTefOX.js +1 -0
- package/dist/assets/{architectureDiagram-5GKGNRK7-CiRaYU5G.js → architectureDiagram-5GKGNRK7-7iukRoC9.js} +1 -1
- package/dist/assets/{blockDiagram-I7D4REHJ-BK3JcsXA.js → blockDiagram-I7D4REHJ-cuhpQB60.js} +1 -1
- package/dist/assets/{c4Diagram-7LVT6UL2-BbMz-zwF.js → c4Diagram-7LVT6UL2-CMQEYn6T.js} +1 -1
- package/dist/assets/{cache-panel-DxdmvFwH.js → cache-panel-C0oB1kE2.js} +1 -1
- package/dist/assets/{cells-BcQ8Tm3j.js → cells-CzugpTQu.js} +33 -33
- package/dist/assets/channel-CGdeorQk.js +1 -0
- package/dist/assets/{chat-display-TI342pMF.js → chat-display-DevO4F8W.js} +1 -1
- package/dist/assets/chat-panel-BGw0bqDk.js +4 -0
- package/dist/assets/{chat-ui-B1aeiWdr.js → chat-ui-CZZFGlZi.js} +4 -4
- package/dist/assets/{chunk-4HAMMTFA-F3WVmmmo.js → chunk-4HAMMTFA-C6Tl7kEY.js} +1 -1
- package/dist/assets/{chunk-75Z2AOVW-DA_lihEi.js → chunk-75Z2AOVW-BzzYECN7.js} +1 -1
- package/dist/assets/{chunk-DU6HZSFF-J-califE.js → chunk-DU6HZSFF-BlpgvKzl.js} +2 -2
- package/dist/assets/{chunk-F27PBJKO-BzJrz4xx.js → chunk-F27PBJKO-BVIgLGPF.js} +1 -1
- package/dist/assets/{chunk-GMAD6QVW-mXoq4V-T.js → chunk-GMAD6QVW-hv6IsAru.js} +1 -1
- package/dist/assets/{chunk-GVQU2GXP-B-87l3Xt.js → chunk-GVQU2GXP-Duv4ZYb-.js} +1 -1
- package/dist/assets/{chunk-IMKFNOWR-yy9Ec7ZM.js → chunk-IMKFNOWR-BIILwBhp.js} +1 -1
- package/dist/assets/{chunk-L3NEJ4N5-DLeZO9l0.js → chunk-L3NEJ4N5-B2hafvRU.js} +1 -1
- package/dist/assets/{chunk-OSK3NFVY-PWoOtECb.js → chunk-OSK3NFVY-SNGRdtNA.js} +1 -1
- package/dist/assets/{chunk-P2QGCYS3-DsJshZeL.js → chunk-P2QGCYS3-_SeE0QXb.js} +1 -1
- package/dist/assets/{chunk-POPQ4Y6H-CCg5TpSp.js → chunk-POPQ4Y6H-BB5NNJFE.js} +1 -1
- package/dist/assets/{chunk-PWAF6VOD-CrFPAGFi.js → chunk-PWAF6VOD-BZoBqp8x.js} +1 -1
- package/dist/assets/{chunk-SHT3W25Y-lri4OQTX.js → chunk-SHT3W25Y-BtLGdeDB.js} +1 -1
- package/dist/assets/{chunk-SVP7TREG-B3ISFgJc.js → chunk-SVP7TREG-B0UfxhUP.js} +1 -1
- package/dist/assets/{chunk-TICWLB2K-CJ7ETjY3.js → chunk-TICWLB2K-BP7vel_P.js} +1 -1
- package/dist/assets/classDiagram-ZZMXUADV-CoNXKNMZ.js +1 -0
- package/dist/assets/classDiagram-v2-VYDZK3BY-CoNXKNMZ.js +1 -0
- package/dist/assets/code-visibility-BFNYI7hr.js +1 -0
- package/dist/assets/command-palette-DN4JoOLD.js +1 -0
- package/dist/assets/{common-cDzShymV.js → common-DUCPhoyP.js} +1 -1
- package/dist/assets/{components-B3KlHXnD.js → components-CK7CeYlo.js} +1 -1
- package/dist/assets/components-DAmFBASm.js +1 -0
- package/dist/assets/config-B4VJJibu.js +1 -0
- package/dist/assets/{utils-B3E7otfg.js → config-BxZgoFvi.js} +1 -1
- package/dist/assets/config-DpGPGznL.js +1 -0
- package/dist/assets/{cynefinDiagram-5FMLGOSQ-1zl1wSTB.js → cynefinDiagram-5FMLGOSQ-BARZRQBb.js} +1 -1
- package/dist/assets/{dagre-DM-Ch8wC.js → dagre-DVyyr7Cb.js} +1 -1
- package/dist/assets/{dagre-GXQ25YYZ-BqoUeivc.js → dagre-GXQ25YYZ-79s0lOP0.js} +1 -1
- package/dist/assets/data-grid-overlay-editor-DPfnIudL.js +1 -0
- package/dist/assets/{dependency-graph-panel-C-SFFOPF.js → dependency-graph-panel-BN4CLayU.js} +3 -3
- package/dist/assets/{diagram-S7CK7UJ4-CF90sb1m.js → diagram-S7CK7UJ4-BCviGhvd.js} +1 -1
- package/dist/assets/{diagram-UQ7AKVKN-DgbZAUCH.js → diagram-UQ7AKVKN-Ds-BT46T.js} +1 -1
- package/dist/assets/{diagram-VSXAHHWV-DOQllF_r.js → diagram-VSXAHHWV-CK4GkM-q.js} +1 -1
- package/dist/assets/{diagram-VX7I27RA-6dC0a_-i.js → diagram-VX7I27RA-BtL2eh0t.js} +1 -1
- package/dist/assets/{diagram-Z3DM3KII-C1OXYmlB.js → diagram-Z3DM3KII-C6SmbLBS.js} +1 -1
- package/dist/assets/{dist-0HQ0BMF5.js → dist-CQvJDryh.js} +1 -1
- package/dist/assets/{documentation-panel-idK3s0j3.js → documentation-panel-CzRbXV5u.js} +1 -1
- package/dist/assets/{ebnfDiagram-PWID7BFC-7gC3cuC7.js → ebnfDiagram-PWID7BFC-D_JU4cMQ.js} +1 -1
- package/dist/assets/edit-page-jGNYBqQo.js +174 -0
- package/dist/assets/{erDiagram-RLTQ6QDP-BsE87b_i.js → erDiagram-RLTQ6QDP-Z9MJ6X-r.js} +1 -1
- package/dist/assets/{error-entries-C-LFkdXb.js → error-entries-CDSsy76p.js} +1 -1
- package/dist/assets/{error-panel-YAFgCest.js → error-panel-ABvjFG3p.js} +1 -1
- package/dist/assets/{feedback-button-sRQQL2TH.js → feedback-button-CZhLiwkO.js} +2 -2
- package/dist/assets/file-explorer-panel-DTiEpmVE.js +1 -0
- package/dist/assets/file-name-input-C1C8xrjA.js +3 -0
- package/dist/assets/filename-fxG7K_MU.js +1 -0
- package/dist/assets/flowDiagram-HODETNUW-ncLciK2K.js +1 -0
- package/dist/assets/{gallery-page-CH8TM7bV.js → gallery-page-Moi4ZdJ0.js} +1 -1
- package/dist/assets/{ganttDiagram-EL5Y4UJY-d9Qqbnau.js → ganttDiagram-EL5Y4UJY-Cgy11k0G.js} +1 -1
- package/dist/assets/{gitGraphDiagram-WWUBYQGX-DBunV_fz.js → gitGraphDiagram-WWUBYQGX-CVH8fay_.js} +1 -1
- package/dist/assets/glide-data-editor-CbsCWu-K.js +131 -0
- package/dist/assets/{graphlib-DDJ8O6ZH.js → graphlib-3maKwbRO.js} +1 -1
- package/dist/assets/has-cJsIKK9t.js +1 -0
- package/dist/assets/home-page-UAgVOkCZ.js +4 -0
- package/dist/assets/index--sT8xf-8.css +2 -0
- package/dist/assets/index-DafnX1Xx.js +62 -0
- package/dist/assets/{infoDiagram-27XIBGKW-FE7De1I6.js → infoDiagram-27XIBGKW-Dk8U81nJ.js} +1 -1
- package/dist/assets/{ishikawaDiagram-5VMMS53U-CSEGYrJ-.js → ishikawaDiagram-5VMMS53U-CzHQlMUQ.js} +1 -1
- package/dist/assets/{journeyDiagram-3NMN7TZE-Cp4mxQrE.js → journeyDiagram-3NMN7TZE-DRc8ACzh.js} +1 -1
- package/dist/assets/{kanban-definition-UXKFOSKX-d7iAZsOb.js → kanban-definition-UXKFOSKX-CVyVYiI6.js} +1 -1
- package/dist/assets/katex-DkE0ukCy.js +257 -0
- package/dist/assets/{logs-panel-xMj01DRN.js → logs-panel-Bz5ysVX4.js} +1 -1
- package/dist/assets/{mermaid-CNjXdDrA.js → mermaid-D_jPUVqz.js} +4 -4
- package/dist/assets/mhchem-CmzEtQj8.js +1 -0
- package/dist/assets/{mindmap-definition-YA3MSWOX-CddM9_uP.js → mindmap-definition-YA3MSWOX-D5t6Aq5Z.js} +1 -1
- package/dist/assets/{number-overlay-editor-N57if5NT.js → number-overlay-editor-FAD8sjFU.js} +1 -1
- package/dist/assets/{outline-panel-Garp-f5k.js → outline-panel-85oiQ0XX.js} +1 -1
- package/dist/assets/packages-panel-ClKIaP5S.js +1 -0
- package/dist/assets/{panels-CnbmgzkS.js → panels-B48zp96D.js} +9 -9
- package/dist/assets/{pegDiagram-XKGWAZYB-CEns8d-M.js → pegDiagram-XKGWAZYB-BTkTm4ej.js} +1 -1
- package/dist/assets/{pieDiagram-E7YTZNPT-wyb0xQ0R.js → pieDiagram-E7YTZNPT-Bq6X88M1.js} +1 -1
- package/dist/assets/{process-output-ObDfw1uu.js → process-output-Cg2ta3vY.js} +1 -1
- package/dist/assets/{quadrantDiagram-AXDQQJYC-BE-xlTLH.js → quadrantDiagram-AXDQQJYC-D_wrOwLk.js} +1 -1
- package/dist/assets/{railroadDiagram-O6MQD6OU-DZZVHmr2.js → railroadDiagram-O6MQD6OU-fv8HXW_g.js} +1 -1
- package/dist/assets/{requirementDiagram-BXWQKSXE-C3Bf4dd5.js → requirementDiagram-BXWQKSXE-DDOOMUfC.js} +1 -1
- package/dist/assets/{reveal-component-DgrXq3Kv.js → reveal-component-CR43gpTZ.js} +3 -3
- package/dist/assets/run-page-NR_KFWk8.js +1 -0
- package/dist/assets/{sankeyDiagram-P5KCCOFB-BWLQCyjK.js → sankeyDiagram-P5KCCOFB-CvvUY6tS.js} +1 -1
- package/dist/assets/scratchpad-panel-DHc-oVb1.js +1 -0
- package/dist/assets/{secrets-panel-4dRIaZfY.js → secrets-panel-C-65ZjnM.js} +1 -1
- package/dist/assets/{sequenceDiagram-WJ2MYXX4-BIY0mgX7.js → sequenceDiagram-WJ2MYXX4-DdpBhQaS.js} +1 -1
- package/dist/assets/session-DXKMv4Bs.js +1 -0
- package/dist/assets/session-panel-DiwQqIbt.js +1 -0
- package/dist/assets/{SelectionIndicator-DLJBcP4e.js → shell-BSnvn9t7.js} +1 -1
- package/dist/assets/snippets-panel-BLmXrneD.js +1 -0
- package/dist/assets/{stateDiagram-D77RDMKH-D_xRHRLf.js → stateDiagram-D77RDMKH-D3p19fac.js} +1 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-CplsDaDl.js +1 -0
- package/dist/assets/{swimlanes-42K2YHIH-CDQzpfz0.js → swimlanes-42K2YHIH-CLxCSvU4.js} +1 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-DPoIFx8T.js +8 -0
- package/dist/assets/{swiper-component-j-Lk25wW.js → swiper-component-rqJChT_P.js} +1 -1
- package/dist/assets/{terminal-BToH-U1t.js → terminal-CY4h1UmO.js} +1 -1
- package/dist/assets/{timeline-definition-24CTP7MA-D5Jgj1fz.js → timeline-definition-24CTP7MA-Cw2PY--x.js} +1 -1
- package/dist/assets/tooltip-PLkI_UfK.js +61 -0
- package/dist/assets/{tracing-DtKLmNHj.js → tracing-Q8NAdlrA.js} +1 -1
- package/dist/assets/tracing-panel-Ci4kY4dr.js +2 -0
- package/dist/assets/useCellActionButton-DpYtVfFe.js +1 -0
- package/dist/assets/useTheme-DcnXlv0_.js +1 -0
- package/dist/assets/{constants-BDbGiS7O.js → utils-B4K466FK.js} +1 -1
- package/dist/assets/{vega-component-DVSvu4vP.js → vega-component-BpoSew4K.js} +1 -1
- package/dist/assets/{vega-embed-container-QM1zZmYi.js → vega-embed-container-BOJG8mCA.js} +1 -1
- package/dist/assets/{vennDiagram-4TSXK5OY-Cv6jxbpX.js → vennDiagram-4TSXK5OY-D8bN6lVl.js} +1 -1
- package/dist/assets/{wardleyDiagram-VM6X3IG4-lwv-YoWo.js → wardleyDiagram-VM6X3IG4-CbZdo0SA.js} +1 -1
- package/dist/assets/{write-secret-modal-CBUZw27N.js → write-secret-modal-BiG2tahJ.js} +1 -1
- package/dist/assets/{xychartDiagram-S5SC5T6Z-CGM4LlFN.js → xychartDiagram-S5SC5T6Z-DUUQ9tau.js} +1 -1
- package/dist/assets/youtube-A9VT7NHb.js +1 -0
- package/dist/index.html +14 -13
- package/package.json +1 -2
- package/src/__mocks__/common.ts +3 -3
- package/src/__tests__/CellStatus.test.tsx +1 -1
- package/src/__tests__/setup.ts +7 -7
- package/src/components/app-config/ai-config.tsx +2 -0
- package/src/components/app-config/optional-features.tsx +1 -1
- package/src/components/chat/chat-panel.tsx +19 -8
- package/src/components/data-table/TableTopBar.tsx +2 -2
- package/src/components/data-table/__tests__/export-actions.test.tsx +662 -0
- package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +286 -0
- package/src/components/data-table/charts/charts.tsx +26 -13
- package/src/components/data-table/column-summary/chart-spec-model.tsx +4 -4
- package/src/components/data-table/data-table.tsx +1 -1
- package/src/components/data-table/export-actions.tsx +642 -176
- package/src/components/data-table/schemas.ts +25 -3
- package/src/components/dependency-graph/elements.ts +2 -2
- package/src/components/editor/__tests__/package-alert.test.tsx +140 -0
- package/src/components/editor/actions/__tests__/pair-with-agent.test.tsx +107 -0
- package/src/components/editor/actions/pair-with-agent.tsx +119 -0
- package/src/components/editor/actions/useNotebookActions.tsx +2 -1
- package/src/components/editor/ai/add-cell-with-ai.tsx +2 -0
- package/src/components/editor/ai/staged-cell-submission.ts +2 -2
- package/src/components/editor/ai/transport/chat-transport.tsx +1 -1
- package/src/components/editor/chrome/components/contribute-snippet-button.tsx +6 -1
- package/src/components/editor/connections/database/as-code.ts +44 -44
- package/src/components/editor/connections/storage/as-code.ts +3 -3
- package/src/components/editor/file-tree/requesting-tree.tsx +16 -13
- package/src/components/editor/navigation/clipboard.ts +2 -2
- package/src/components/editor/notebook-cell.tsx +2 -2
- package/src/components/editor/output/MarimoTracebackOutput.tsx +1 -1
- package/src/components/editor/output/VideoOutput.tsx +1 -1
- package/src/components/editor/output/ansi-reduce.ts +21 -17
- package/src/components/editor/output/console/ConsoleOutput.tsx +3 -3
- package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +59 -0
- package/src/components/editor/output/console/text-rendering.tsx +3 -1
- package/src/components/editor/package-alert.tsx +20 -7
- package/src/components/home/components.tsx +1 -0
- package/src/components/markdown/markdown-renderer.css +1 -1
- package/src/components/pages/run-page.tsx +1 -0
- package/src/components/slides/__tests__/minimap-actions.test.tsx +7 -7
- package/src/components/slides/__tests__/minimap.test.ts +12 -12
- package/src/components/slides/__tests__/slide-scroll-hint.test.tsx +8 -8
- package/src/components/static-html/__tests__/share-modal.test.tsx +154 -0
- package/src/components/static-html/__tests__/stage-for-publish.test.ts +111 -0
- package/src/components/static-html/share-modal.tsx +114 -144
- package/src/components/static-html/stage-for-publish.ts +56 -0
- package/src/components/static-html/static-banner.tsx +2 -2
- package/src/components/ui/links.tsx +6 -1
- package/src/components/ui/typography.tsx +16 -8
- package/src/core/ai/context/__tests__/registry.test.ts +31 -28
- package/src/core/ai/context/providers/cell-output.ts +8 -8
- package/src/core/ai/context/providers/datasource.ts +10 -7
- package/src/core/ai/context/providers/error.ts +7 -7
- package/src/core/ai/context/providers/file.ts +8 -8
- package/src/core/ai/context/providers/tables.ts +7 -7
- package/src/core/ai/context/providers/variable.ts +7 -7
- package/src/core/ai/context/registry.ts +18 -18
- package/src/core/ai/ids/ids.ts +5 -5
- package/src/core/ai/model-registry.ts +12 -9
- package/src/core/ai/staged-cells.ts +6 -6
- package/src/core/ai/tools/base.ts +6 -6
- package/src/core/ai/tools/edit-notebook-tool.ts +6 -6
- package/src/core/ai/tools/registry.ts +4 -4
- package/src/core/ai/tools/run-cells-tool.ts +7 -7
- package/src/core/ai/tools/sample-tool.ts +6 -6
- package/src/core/cells/__tests__/cells.test.ts +27 -0
- package/src/core/cells/cells.ts +2 -1
- package/src/core/codemirror/cells/debugger-decorations.ts +13 -7
- package/src/core/codemirror/cells/line-timing-decorations.ts +9 -9
- package/src/core/codemirror/cells/traceback-decorations.ts +7 -4
- package/src/core/codemirror/code-lens/extension.ts +13 -13
- package/src/core/codemirror/copilot/__tests__/language-server.test.ts +6 -6
- package/src/core/codemirror/copilot/__tests__/transport.test.ts +12 -12
- package/src/core/codemirror/copilot/language-server.ts +13 -13
- package/src/core/codemirror/copilot/transport.ts +1 -1
- package/src/core/codemirror/find-replace/search-highlight.ts +5 -5
- package/src/core/codemirror/go-to-definition/underline.ts +3 -3
- package/src/core/codemirror/keymaps/vim.ts +3 -3
- package/src/core/codemirror/language/languages/markdown.ts +8 -8
- package/src/core/codemirror/language/languages/python.ts +7 -7
- package/src/core/codemirror/language/languages/sql/completion-builder.ts +5 -5
- package/src/core/codemirror/language/languages/sql/completion-store.ts +4 -4
- package/src/core/codemirror/language/languages/sql/sql.ts +21 -21
- package/src/core/codemirror/lsp/__tests__/transports.test.ts +10 -10
- package/src/core/codemirror/lsp/federated-lsp.ts +29 -27
- package/src/core/codemirror/lsp/notebook-lsp.ts +21 -21
- package/src/core/codemirror/misc/__tests__/dnd.test.ts +10 -10
- package/src/core/codemirror/misc/__tests__/paste.test.ts +7 -4
- package/src/core/codemirror/reactive-references/extension.ts +3 -3
- package/src/core/codemirror/rtc/loro/awareness.ts +12 -12
- package/src/core/codemirror/rtc/loro/sync.ts +5 -5
- package/src/core/codemirror/vim/cursor-visibility.ts +3 -3
- package/src/core/dom/ui-element.ts +5 -5
- package/src/core/dom/uiregistry.ts +10 -10
- package/src/core/hotkeys/hotkeys.ts +9 -9
- package/src/core/islands/__tests__/bridge.test.ts +4 -4
- package/src/core/islands/__tests__/test-utils.tsx +4 -4
- package/src/core/islands/bridge.ts +85 -82
- package/src/core/islands/components/web-components.tsx +7 -7
- package/src/core/islands/worker/__tests__/controller.test.ts +1 -1
- package/src/core/islands/worker/controller.ts +6 -3
- package/src/core/islands/worker-factory.ts +2 -2
- package/src/core/kernel/RuntimeState.ts +4 -4
- package/src/core/kernel/__tests__/session.test.ts +110 -1
- package/src/core/kernel/session.ts +46 -15
- package/src/core/lsp/__tests__/transport.test.ts +19 -19
- package/src/core/lsp/transport.ts +5 -5
- package/src/core/network/CachingRequestRegistry.ts +2 -2
- package/src/core/network/DeferredRequestRegistry.ts +4 -4
- package/src/core/network/__tests__/export-asset-url.test.ts +45 -0
- package/src/core/network/export-asset-url.ts +19 -0
- package/src/core/network/requests-network.ts +2 -7
- package/src/core/runtime/runtime.ts +25 -20
- package/src/core/static/virtual-file-tracker.ts +5 -5
- package/src/core/wasm/__tests__/bridge.test.ts +2 -2
- package/src/core/wasm/bridge.ts +132 -133
- package/src/core/wasm/router.ts +5 -5
- package/src/core/wasm/store.ts +4 -4
- package/src/core/wasm/worker/bootstrap.ts +8 -5
- package/src/core/wasm/worker/message-buffer.ts +3 -3
- package/src/core/websocket/transports/__tests__/ws.test.ts +8 -8
- package/src/core/websocket/transports/basic.ts +15 -8
- package/src/core/websocket/transports/sse.ts +7 -7
- package/src/core/websocket/transports/transport.ts +3 -3
- package/src/core/websocket/transports/ws.ts +9 -7
- package/src/css/globals.css +18 -21
- package/src/css/katex.min.css +260 -241
- package/src/hooks/__tests__/useOverflowDetection.test.tsx +5 -5
- package/src/hooks/__tests__/useResizeObserver.test.tsx +12 -12
- package/src/plugins/core/registerReactComponent.tsx +5 -5
- package/src/plugins/core/sidebar-element.tsx +2 -2
- package/src/plugins/impl/ButtonPlugin.tsx +3 -3
- package/src/plugins/impl/CheckboxPlugin.tsx +3 -3
- package/src/plugins/impl/CodeEditorPlugin.tsx +3 -3
- package/src/plugins/impl/DatePickerPlugin.tsx +3 -3
- package/src/plugins/impl/DateRangePlugin.tsx +3 -3
- package/src/plugins/impl/DateTimePickerPlugin.tsx +3 -3
- package/src/plugins/impl/DictPlugin.tsx +3 -3
- package/src/plugins/impl/DropdownPlugin.tsx +3 -3
- package/src/plugins/impl/FileUploadPlugin.tsx +3 -3
- package/src/plugins/impl/MatrixPlugin.tsx +4 -4
- package/src/plugins/impl/MicrophonePlugin.tsx +3 -3
- package/src/plugins/impl/MultiselectPlugin.tsx +3 -3
- package/src/plugins/impl/NumberPlugin.tsx +3 -3
- package/src/plugins/impl/RadioPlugin.tsx +9 -5
- package/src/plugins/impl/RangeSliderPlugin.tsx +3 -3
- package/src/plugins/impl/RefreshPlugin.tsx +3 -3
- package/src/plugins/impl/SliderPlugin.tsx +3 -3
- package/src/plugins/impl/SwitchPlugin.tsx +3 -3
- package/src/plugins/impl/TabsPlugin.tsx +3 -3
- package/src/plugins/impl/TextAreaPlugin.tsx +3 -3
- package/src/plugins/impl/TextInputPlugin.tsx +3 -3
- package/src/plugins/impl/__tests__/MatrixPlugin.test.tsx +2 -2
- package/src/plugins/impl/anywidget/model.ts +10 -10
- package/src/plugins/impl/anywidget/registry.ts +12 -9
- package/src/plugins/impl/anywidget/runtime.ts +11 -9
- package/src/plugins/impl/anywidget/widget-binding.ts +6 -6
- package/src/plugins/impl/image-comparison/ImageComparisonComponent.tsx +7 -6
- package/src/plugins/impl/matplotlib/MatplotlibPlugin.tsx +3 -3
- package/src/plugins/impl/matplotlib/__tests__/matplotlib-renderer.test.ts +2 -2
- package/src/plugins/impl/matplotlib/matplotlib-renderer.ts +2 -2
- package/src/plugins/impl/mpl-interactive/mpl-websocket-shim.ts +10 -10
- package/src/plugins/impl/panel/utils.ts +5 -5
- package/src/plugins/impl/plotly/PlotlyPlugin.tsx +3 -3
- package/src/plugins/impl/vega/VegaPlugin.tsx +3 -3
- package/src/plugins/impl/vega/__tests__/use-vega-container-remeasure.test.ts +7 -7
- package/src/plugins/impl/vega/__tests__/utils.test.ts +16 -1
- package/src/plugins/impl/vega/__tests__/vega.test.ts +37 -0
- package/src/plugins/impl/vega/loader.ts +23 -1
- package/src/plugins/impl/vega/utils.ts +5 -4
- package/src/plugins/layout/AccordionPlugin.tsx +3 -3
- package/src/plugins/layout/CalloutPlugin.tsx +3 -3
- package/src/plugins/layout/ImageComparisonPlugin.tsx +5 -3
- package/src/plugins/layout/JsonOutputPlugin.tsx +3 -3
- package/src/plugins/layout/MimeRenderPlugin.tsx +3 -3
- package/src/plugins/layout/NavigationMenuPlugin.tsx +3 -3
- package/src/plugins/layout/OutlinePlugin.tsx +3 -3
- package/src/plugins/layout/ProgressPlugin.tsx +3 -3
- package/src/plugins/layout/RoutesPlugin.tsx +6 -8
- package/src/plugins/layout/StatPlugin.tsx +3 -3
- package/src/plugins/layout/TexPlugin.tsx +3 -3
- package/src/plugins/layout/carousel/CarouselPlugin.tsx +4 -4
- package/src/plugins/layout/mermaid/MermaidPlugin.tsx +3 -3
- package/src/utils/Deferred.ts +6 -6
- package/src/utils/async-capture-tracker.ts +10 -7
- package/src/utils/errors.ts +5 -5
- package/src/utils/id-tree.tsx +83 -80
- package/src/utils/lru.ts +1 -1
- package/src/utils/multi-map.ts +14 -12
- package/src/utils/paths.ts +6 -6
- package/src/utils/pluralize.ts +3 -3
- package/src/utils/progress.ts +6 -6
- package/src/utils/python-poet/poet.ts +14 -14
- package/src/utils/routes.ts +4 -2
- package/src/utils/semaphore.ts +6 -6
- package/src/utils/storage/storage.ts +6 -6
- package/src/utils/storage/typed.ts +16 -12
- package/src/utils/time.ts +9 -9
- package/src/utils/timed-cache.ts +4 -4
- package/src/utils/tracer.ts +9 -6
- package/dist/assets/LazyAnyLanguageCodeMirror-EBegnOE0.js +0 -2
- package/dist/assets/add-cell-with-ai-Ovbsj95O.js +0 -76
- package/dist/assets/ai-model-dropdown-1t7m1iFc.js +0 -3
- package/dist/assets/app-config-button-vCxNGhe2.js +0 -1
- package/dist/assets/channel-ni7ei-yz.js +0 -1
- package/dist/assets/chat-panel-CzdR8RG_.js +0 -4
- package/dist/assets/classDiagram-ZZMXUADV-CIEFh40S.js +0 -1
- package/dist/assets/classDiagram-v2-VYDZK3BY-CIEFh40S.js +0 -1
- package/dist/assets/code-visibility-G0W4ym3Y.js +0 -1
- package/dist/assets/command-palette-DhdK67bL.js +0 -1
- package/dist/assets/components-CQVZgYvR.js +0 -1
- package/dist/assets/config-CMqNngzA.js +0 -1
- package/dist/assets/config-CmBpjGXk.js +0 -1
- package/dist/assets/data-grid-overlay-editor-DbqMOWZn.js +0 -1
- package/dist/assets/edit-page-_D_Nv3dJ.js +0 -176
- package/dist/assets/file-explorer-panel-LAa-LxTN.js +0 -1
- package/dist/assets/file-name-input-BXq6eIDs.js +0 -3
- package/dist/assets/flowDiagram-HODETNUW-B4xRBXB1.js +0 -1
- package/dist/assets/glide-data-editor-DPMjaN4R.js +0 -131
- package/dist/assets/has-CanCEarv.js +0 -1
- package/dist/assets/home-page-vDWLpS4F.js +0 -4
- package/dist/assets/index-2TQenOiQ.css +0 -2
- package/dist/assets/index-CgQsZfww.js +0 -62
- package/dist/assets/katex-mNPPSsir.js +0 -257
- package/dist/assets/katex-ukvJUC1e.js +0 -257
- package/dist/assets/mhchem-DcHTrqjv.js +0 -1
- package/dist/assets/packages-panel-B8HU9FAM.js +0 -1
- package/dist/assets/run-page-E0qMru0p.js +0 -1
- package/dist/assets/scratchpad-panel-CCFyaegO.js +0 -1
- package/dist/assets/session-C8nQMsOE.js +0 -1
- package/dist/assets/session-panel-CR3BHLhQ.js +0 -1
- package/dist/assets/snippets-panel-Dn22yTr_.js +0 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-BQvYFx_a.js +0 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-BAn2JKhB.js +0 -8
- package/dist/assets/tooltip-B1vGi-z4.js +0 -61
- package/dist/assets/tracing-panel-C5ybTQyJ.js +0 -2
- package/dist/assets/useCellActionButton-Bm9OZdcq.js +0 -1
- package/dist/assets/useTheme-Ho3YdAx3.js +0 -1
- package/dist/assets/youtube-ELpGJEdR.js +0 -1
|
@@ -51,7 +51,7 @@ export class MarimoIslandElement extends HTMLElement {
|
|
|
51
51
|
public static readonly editorTagName = ISLAND_TAG_NAMES.CODE_EDITOR;
|
|
52
52
|
public static readonly styleNamespace = ISLAND_CSS_CLASSES.NAMESPACE;
|
|
53
53
|
|
|
54
|
-
constructor() {
|
|
54
|
+
public constructor() {
|
|
55
55
|
super();
|
|
56
56
|
this.classList.add(MarimoIslandElement.styleNamespace);
|
|
57
57
|
}
|
|
@@ -59,7 +59,7 @@ export class MarimoIslandElement extends HTMLElement {
|
|
|
59
59
|
/**
|
|
60
60
|
* Gets the app ID from the element's data attribute
|
|
61
61
|
*/
|
|
62
|
-
get appId(): string {
|
|
62
|
+
public get appId(): string {
|
|
63
63
|
const appId = this.getAttribute(ISLAND_DATA_ATTRIBUTES.APP_ID);
|
|
64
64
|
invariant(appId, "Missing data-app-id attribute");
|
|
65
65
|
return appId;
|
|
@@ -68,7 +68,7 @@ export class MarimoIslandElement extends HTMLElement {
|
|
|
68
68
|
/**
|
|
69
69
|
* Whether this island is reactive (has code sent to Python for execution)
|
|
70
70
|
*/
|
|
71
|
-
get isReactive(): boolean {
|
|
71
|
+
public get isReactive(): boolean {
|
|
72
72
|
return this.getAttribute(ISLAND_DATA_ATTRIBUTES.REACTIVE) === "true";
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -76,7 +76,7 @@ export class MarimoIslandElement extends HTMLElement {
|
|
|
76
76
|
* Gets the cell ID by looking up the cell index in the notebook state.
|
|
77
77
|
* Returns undefined for non-reactive islands (they have no corresponding cell).
|
|
78
78
|
*/
|
|
79
|
-
get cellId(): CellId | undefined {
|
|
79
|
+
public get cellId(): CellId | undefined {
|
|
80
80
|
if (!this.isReactive) {
|
|
81
81
|
return undefined;
|
|
82
82
|
}
|
|
@@ -95,7 +95,7 @@ export class MarimoIslandElement extends HTMLElement {
|
|
|
95
95
|
/**
|
|
96
96
|
* Gets the code for this island cell
|
|
97
97
|
*/
|
|
98
|
-
get code(): string {
|
|
98
|
+
public get code(): string {
|
|
99
99
|
return extractIslandCodeFromEmbed(this);
|
|
100
100
|
}
|
|
101
101
|
|
|
@@ -115,7 +115,7 @@ export class MarimoIslandElement extends HTMLElement {
|
|
|
115
115
|
* which can happen inside a React render cycle. Rendering
|
|
116
116
|
* synchronously from there causes "unmount during render" warnings.
|
|
117
117
|
*/
|
|
118
|
-
connectedCallback(): void {
|
|
118
|
+
public connectedCallback(): void {
|
|
119
119
|
const connectionGeneration = ++this.connectionGeneration;
|
|
120
120
|
this.addEventListener(
|
|
121
121
|
ISLAND_SOURCE_CHANGED_EVENT,
|
|
@@ -296,7 +296,7 @@ export class MarimoIslandElement extends HTMLElement {
|
|
|
296
296
|
/**
|
|
297
297
|
* Cleanup when element is removed from DOM
|
|
298
298
|
*/
|
|
299
|
-
disconnectedCallback(): void {
|
|
299
|
+
public disconnectedCallback(): void {
|
|
300
300
|
this.removeEventListener(
|
|
301
301
|
ISLAND_SOURCE_CHANGED_EVENT,
|
|
302
302
|
this.handleSourceChanged,
|
|
@@ -5,7 +5,7 @@ import { describe, expect, it, vi } from "vitest";
|
|
|
5
5
|
import { ReadonlyWasmController } from "../controller";
|
|
6
6
|
|
|
7
7
|
class TestController extends ReadonlyWasmController {
|
|
8
|
-
setPyodide(pyodide: PyodideInterface) {
|
|
8
|
+
public setPyodide(pyodide: PyodideInterface) {
|
|
9
9
|
this.pyodide = pyodide;
|
|
10
10
|
}
|
|
11
11
|
}
|
|
@@ -9,7 +9,7 @@ import type { JsonString } from "@/utils/json/base64";
|
|
|
9
9
|
import { Logger } from "../../../utils/Logger";
|
|
10
10
|
|
|
11
11
|
export class ReadonlyWasmController extends DefaultWasmController {
|
|
12
|
-
override async bootstrap(opts: {
|
|
12
|
+
public override async bootstrap(opts: {
|
|
13
13
|
version: string;
|
|
14
14
|
pyodideVersion: string;
|
|
15
15
|
}): Promise<PyodideInterface> {
|
|
@@ -17,7 +17,10 @@ export class ReadonlyWasmController extends DefaultWasmController {
|
|
|
17
17
|
return pyodide;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
override async mountFilesystem(opts: {
|
|
20
|
+
public override async mountFilesystem(opts: {
|
|
21
|
+
code: string;
|
|
22
|
+
filename: string;
|
|
23
|
+
}) {
|
|
21
24
|
const { code, filename } = opts;
|
|
22
25
|
// Write file
|
|
23
26
|
try {
|
|
@@ -34,7 +37,7 @@ export class ReadonlyWasmController extends DefaultWasmController {
|
|
|
34
37
|
return { code, filename };
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
override async startSession(opts: {
|
|
40
|
+
public override async startSession(opts: {
|
|
38
41
|
code: string;
|
|
39
42
|
filename: string | null;
|
|
40
43
|
onMessage: (message: JsonString<NotificationPayload>) => void;
|
|
@@ -39,7 +39,7 @@ export class DefaultWorkerFactory implements WorkerFactory {
|
|
|
39
39
|
private readonly url: string;
|
|
40
40
|
private readonly name: string;
|
|
41
41
|
|
|
42
|
-
constructor(config: DefaultWorkerFactoryConfig = {}) {
|
|
42
|
+
public constructor(config: DefaultWorkerFactoryConfig = {}) {
|
|
43
43
|
this.url = config.workerUrl || this.getDefaultWorkerUrl();
|
|
44
44
|
this.name = config.workerName || getMarimoVersion();
|
|
45
45
|
}
|
|
@@ -47,7 +47,7 @@ export class DefaultWorkerFactory implements WorkerFactory {
|
|
|
47
47
|
/**
|
|
48
48
|
* Creates a new Pyodide worker
|
|
49
49
|
*/
|
|
50
|
-
create(): Worker {
|
|
50
|
+
public create(): Worker {
|
|
51
51
|
return createModuleWorker(new URL(this.url, import.meta.url), {
|
|
52
52
|
name: this.name,
|
|
53
53
|
});
|
|
@@ -17,7 +17,7 @@ export class RuntimeState {
|
|
|
17
17
|
/**
|
|
18
18
|
* Shared instance of RuntimeState since this must be a singleton.
|
|
19
19
|
*/
|
|
20
|
-
static get INSTANCE(): RuntimeState {
|
|
20
|
+
public static get INSTANCE(): RuntimeState {
|
|
21
21
|
const KEY = "_marimo_private_RuntimeState";
|
|
22
22
|
if (!window[KEY]) {
|
|
23
23
|
window[KEY] = new RuntimeState(UI_ELEMENT_REGISTRY);
|
|
@@ -31,7 +31,7 @@ export class RuntimeState {
|
|
|
31
31
|
private _sendComponentValues: RunRequests["sendComponentValues"] | undefined;
|
|
32
32
|
private uiElementRegistry: UIElementRegistry;
|
|
33
33
|
|
|
34
|
-
constructor(uiElementRegistry: UIElementRegistry) {
|
|
34
|
+
public constructor(uiElementRegistry: UIElementRegistry) {
|
|
35
35
|
this.uiElementRegistry = uiElementRegistry;
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -45,7 +45,7 @@ export class RuntimeState {
|
|
|
45
45
|
/**
|
|
46
46
|
* Start listening for events from UIElements
|
|
47
47
|
*/
|
|
48
|
-
start(sendComponentValues: RunRequests["sendComponentValues"]) {
|
|
48
|
+
public start(sendComponentValues: RunRequests["sendComponentValues"]) {
|
|
49
49
|
if (this.hasStarted) {
|
|
50
50
|
Logger.warn("RuntimeState already started");
|
|
51
51
|
return;
|
|
@@ -61,7 +61,7 @@ export class RuntimeState {
|
|
|
61
61
|
/**
|
|
62
62
|
* Stop listening for events from UIElements
|
|
63
63
|
*/
|
|
64
|
-
stop() {
|
|
64
|
+
public stop() {
|
|
65
65
|
if (!this.hasStarted) {
|
|
66
66
|
Logger.warn("RuntimeState already stopped");
|
|
67
67
|
return;
|
|
@@ -1,10 +1,119 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import type { AppMode } from "@/core/mode";
|
|
3
4
|
import { generateSessionId, isSessionId } from "../session";
|
|
4
5
|
|
|
6
|
+
const URL_SESSION_ID = "s_abc123";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Load a fresh copy of the session module against the given URL and mode.
|
|
10
|
+
* The module caches its session id, so each case needs its own instance.
|
|
11
|
+
*/
|
|
12
|
+
async function loadSession(opts: { mode: AppMode | undefined; url: string }) {
|
|
13
|
+
vi.resetModules();
|
|
14
|
+
window.history.replaceState({}, "", opts.url);
|
|
15
|
+
const { store } = await import("@/core/state/jotai");
|
|
16
|
+
const { initialModeAtom } = await import("@/core/mode");
|
|
17
|
+
store.set(initialModeAtom, opts.mode);
|
|
18
|
+
return await import("../session");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function currentSearchParams(): URLSearchParams {
|
|
22
|
+
return new URL(window.location.href).searchParams;
|
|
23
|
+
}
|
|
24
|
+
|
|
5
25
|
describe("Session", () => {
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
window.history.replaceState({}, "", "/");
|
|
28
|
+
});
|
|
29
|
+
|
|
6
30
|
it("should create a session", () => {
|
|
7
31
|
const id = generateSessionId();
|
|
8
32
|
expect(isSessionId(id)).toBe(true);
|
|
9
33
|
});
|
|
34
|
+
|
|
35
|
+
it("resumes the session named in the URL in edit mode", async () => {
|
|
36
|
+
const { getSessionId } = await loadSession({
|
|
37
|
+
mode: "edit",
|
|
38
|
+
url: `/?session_id=${URL_SESSION_ID}`,
|
|
39
|
+
});
|
|
40
|
+
expect(getSessionId()).toBe(URL_SESSION_ID);
|
|
41
|
+
expect(currentSearchParams().has("session_id")).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("keeps the URL session id in kiosk mode so a refresh resumes it", async () => {
|
|
45
|
+
const { getSessionId } = await loadSession({
|
|
46
|
+
mode: "edit",
|
|
47
|
+
url: `/?kiosk=true&session_id=${URL_SESSION_ID}`,
|
|
48
|
+
});
|
|
49
|
+
expect(getSessionId()).toBe(URL_SESSION_ID);
|
|
50
|
+
expect(currentSearchParams().get("session_id")).toBe(URL_SESSION_ID);
|
|
51
|
+
expect(currentSearchParams().get("kiosk")).toBe("true");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("keeps the kiosk flag in edit mode when the URL names no session", async () => {
|
|
55
|
+
const { getSessionId } = await loadSession({
|
|
56
|
+
mode: "edit",
|
|
57
|
+
url: "/?kiosk=true",
|
|
58
|
+
});
|
|
59
|
+
expect(isSessionId(getSessionId())).toBe(true);
|
|
60
|
+
expect(currentSearchParams().get("kiosk")).toBe("true");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("starts a new session when the URL session id is malformed", async () => {
|
|
64
|
+
const { getSessionId } = await loadSession({
|
|
65
|
+
mode: "edit",
|
|
66
|
+
url: "/?session_id=not-a-session",
|
|
67
|
+
});
|
|
68
|
+
const id = getSessionId();
|
|
69
|
+
expect(isSessionId(id)).toBe(true);
|
|
70
|
+
expect(id).not.toBe("not-a-session");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("ignores the URL session id in read mode", async () => {
|
|
74
|
+
const { getSessionId } = await loadSession({
|
|
75
|
+
mode: "read",
|
|
76
|
+
url: `/?session_id=${URL_SESSION_ID}`,
|
|
77
|
+
});
|
|
78
|
+
const id = getSessionId();
|
|
79
|
+
expect(isSessionId(id)).toBe(true);
|
|
80
|
+
expect(id).not.toBe(URL_SESSION_ID);
|
|
81
|
+
// The param is still scrubbed so a refresh cannot reintroduce it.
|
|
82
|
+
expect(currentSearchParams().has("session_id")).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("drops both the session id and the kiosk flag in read mode", async () => {
|
|
86
|
+
const { getSessionId } = await loadSession({
|
|
87
|
+
mode: "read",
|
|
88
|
+
url: `/?kiosk=true&session_id=${URL_SESSION_ID}`,
|
|
89
|
+
});
|
|
90
|
+
expect(getSessionId()).not.toBe(URL_SESSION_ID);
|
|
91
|
+
expect(currentSearchParams().has("session_id")).toBe(false);
|
|
92
|
+
// The kiosk flag is forwarded to the websocket URL, and the server
|
|
93
|
+
// refuses kiosk connections to an app, so it must not survive here.
|
|
94
|
+
expect(currentSearchParams().has("kiosk")).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("drops a lone kiosk flag in read mode", async () => {
|
|
98
|
+
const { getSessionId } = await loadSession({
|
|
99
|
+
mode: "read",
|
|
100
|
+
url: "/?kiosk=true",
|
|
101
|
+
});
|
|
102
|
+
expect(isSessionId(getSessionId())).toBe(true);
|
|
103
|
+
expect(currentSearchParams().has("kiosk")).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("ignores the URL session id when the mode is not yet known", async () => {
|
|
107
|
+
const { getSessionId } = await loadSession({
|
|
108
|
+
mode: undefined,
|
|
109
|
+
url: `/?session_id=${URL_SESSION_ID}`,
|
|
110
|
+
});
|
|
111
|
+
expect(getSessionId()).not.toBe(URL_SESSION_ID);
|
|
112
|
+
expect(currentSearchParams().has("session_id")).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("returns the same id on every call", async () => {
|
|
116
|
+
const { getSessionId } = await loadSession({ mode: "read", url: "/" });
|
|
117
|
+
expect(getSessionId()).toBe(getSessionId());
|
|
118
|
+
});
|
|
10
119
|
});
|
|
@@ -4,6 +4,8 @@ import { Logger } from "@/utils/Logger";
|
|
|
4
4
|
import type { TypedString } from "@/utils/typed";
|
|
5
5
|
import { updateQueryParams } from "@/utils/urls";
|
|
6
6
|
import { KnownQueryParams } from "../constants";
|
|
7
|
+
import { initialModeAtom } from "../mode";
|
|
8
|
+
import { store } from "../state/jotai";
|
|
7
9
|
|
|
8
10
|
export type SessionId = TypedString<"SessionId">;
|
|
9
11
|
|
|
@@ -20,31 +22,60 @@ export function isSessionId(value: string | null): value is SessionId {
|
|
|
20
22
|
return /^s_[\da-z]{6}$/.test(value);
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
let sessionId: SessionId | null = null;
|
|
26
|
+
|
|
27
|
+
function resolveSessionId(): SessionId {
|
|
28
|
+
// Only the editor lets the URL steer the connection: kiosk mode and
|
|
29
|
+
// external editors reattach to a running session through `session_id`.
|
|
30
|
+
// An app served with `marimo run` never does. There, the session id is the
|
|
31
|
+
// only thing that routes a browser to its kernel, so a link that picked it
|
|
32
|
+
// would let the link's author attach to the reader's session afterwards,
|
|
33
|
+
// and a stray `kiosk` flag would make the server refuse the connection.
|
|
34
|
+
const urlMaySteerConnection = store.get(initialModeAtom) === "edit";
|
|
35
|
+
|
|
24
36
|
const url = new URL(window.location.href);
|
|
25
37
|
const id = url.searchParams.get(
|
|
26
38
|
KnownQueryParams.sessionId,
|
|
27
39
|
) as SessionId | null;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
// this is so we can resume the same session if the user refreshes the page
|
|
33
|
-
if (params.has(KnownQueryParams.kiosk)) {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
40
|
+
const urlSessionId = isSessionId(id) ? id : null;
|
|
41
|
+
|
|
42
|
+
updateQueryParams((params) => {
|
|
43
|
+
if (!urlMaySteerConnection) {
|
|
36
44
|
params.delete(KnownQueryParams.sessionId);
|
|
45
|
+
params.delete(KnownQueryParams.kiosk);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// Keep the session_id in kiosk mode so that a refresh resumes the same
|
|
49
|
+
// session.
|
|
50
|
+
if (params.has(KnownQueryParams.kiosk)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
params.delete(KnownQueryParams.sessionId);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (urlSessionId === null) {
|
|
57
|
+
Logger.debug("Starting a new session", { sessionId: id });
|
|
58
|
+
return generateSessionId();
|
|
59
|
+
}
|
|
60
|
+
if (!urlMaySteerConnection) {
|
|
61
|
+
Logger.debug("Ignoring session_id from URL outside the editor", {
|
|
62
|
+
sessionId: urlSessionId,
|
|
37
63
|
});
|
|
38
|
-
|
|
39
|
-
return id;
|
|
64
|
+
return generateSessionId();
|
|
40
65
|
}
|
|
41
|
-
Logger.debug("
|
|
42
|
-
return
|
|
43
|
-
}
|
|
66
|
+
Logger.debug("Connecting to existing session", { sessionId: urlSessionId });
|
|
67
|
+
return urlSessionId;
|
|
68
|
+
}
|
|
44
69
|
|
|
45
70
|
/**
|
|
46
|
-
* Resume an existing session or start a new one
|
|
71
|
+
* Resume an existing session or start a new one.
|
|
72
|
+
*
|
|
73
|
+
* Resolved on first use rather than at import time, because the URL is only
|
|
74
|
+
* trusted once the app mode is known.
|
|
47
75
|
*/
|
|
48
76
|
export function getSessionId(): SessionId {
|
|
77
|
+
if (sessionId === null) {
|
|
78
|
+
sessionId = resolveSessionId();
|
|
79
|
+
}
|
|
49
80
|
return sessionId;
|
|
50
81
|
}
|
|
@@ -10,28 +10,28 @@ vi.mock("@/utils/Logger", () => ({
|
|
|
10
10
|
}));
|
|
11
11
|
|
|
12
12
|
class FakeWebSocket {
|
|
13
|
-
static readonly CONNECTING = 0;
|
|
14
|
-
static readonly OPEN = 1;
|
|
15
|
-
static readonly CLOSING = 2;
|
|
16
|
-
static readonly CLOSED = 3;
|
|
17
|
-
static readonly instances: FakeWebSocket[] = [];
|
|
18
|
-
|
|
19
|
-
readonly sent: string[] = [];
|
|
20
|
-
readonly url: string;
|
|
21
|
-
readonly protocols: string | string[] | undefined;
|
|
22
|
-
readyState = FakeWebSocket.CONNECTING;
|
|
13
|
+
public static readonly CONNECTING = 0;
|
|
14
|
+
public static readonly OPEN = 1;
|
|
15
|
+
public static readonly CLOSING = 2;
|
|
16
|
+
public static readonly CLOSED = 3;
|
|
17
|
+
public static readonly instances: FakeWebSocket[] = [];
|
|
18
|
+
|
|
19
|
+
public readonly sent: string[] = [];
|
|
20
|
+
public readonly url: string;
|
|
21
|
+
public readonly protocols: string | string[] | undefined;
|
|
22
|
+
public readyState = FakeWebSocket.CONNECTING;
|
|
23
23
|
private readonly listeners = new Map<
|
|
24
24
|
string,
|
|
25
25
|
Set<(event: { data?: unknown }) => void>
|
|
26
26
|
>();
|
|
27
27
|
|
|
28
|
-
constructor(url: string, protocols?: string | string[]) {
|
|
28
|
+
public constructor(url: string, protocols?: string | string[]) {
|
|
29
29
|
this.url = url;
|
|
30
30
|
this.protocols = protocols;
|
|
31
31
|
FakeWebSocket.instances.push(this);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
addEventListener(
|
|
34
|
+
public addEventListener(
|
|
35
35
|
type: string,
|
|
36
36
|
listener: (event: { data?: unknown }) => void,
|
|
37
37
|
) {
|
|
@@ -40,34 +40,34 @@ class FakeWebSocket {
|
|
|
40
40
|
this.listeners.set(type, listeners);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
send(frame: string) {
|
|
43
|
+
public send(frame: string) {
|
|
44
44
|
this.sent.push(frame);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
close() {
|
|
47
|
+
public close() {
|
|
48
48
|
this.readyState = FakeWebSocket.CLOSED;
|
|
49
49
|
this.emit("close", {});
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
open() {
|
|
52
|
+
public open() {
|
|
53
53
|
this.readyState = FakeWebSocket.OPEN;
|
|
54
54
|
this.emit("open", {});
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
fail() {
|
|
57
|
+
public fail() {
|
|
58
58
|
this.emit("error", {});
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
disconnect() {
|
|
61
|
+
public disconnect() {
|
|
62
62
|
this.readyState = FakeWebSocket.CLOSED;
|
|
63
63
|
this.emit("close", {});
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
receive(message: JSONRPCMessage) {
|
|
66
|
+
public receive(message: JSONRPCMessage) {
|
|
67
67
|
this.emit("message", { data: JSON.stringify(message) });
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
messages(): JSONRPCMessage[] {
|
|
70
|
+
public messages(): JSONRPCMessage[] {
|
|
71
71
|
return this.sent.map((frame) => {
|
|
72
72
|
const message: unknown = JSON.parse(frame);
|
|
73
73
|
if (!isJSONRPCMessage(message)) {
|
|
@@ -100,7 +100,7 @@ export class ReconnectingWebSocketTransport implements Transport {
|
|
|
100
100
|
|
|
101
101
|
public onReconnect: (() => Promise<void>) | undefined;
|
|
102
102
|
|
|
103
|
-
constructor(options: ReconnectingWebSocketTransportOptions) {
|
|
103
|
+
public constructor(options: ReconnectingWebSocketTransportOptions) {
|
|
104
104
|
this.options = {
|
|
105
105
|
...options,
|
|
106
106
|
retries: options.retries ?? 1,
|
|
@@ -109,7 +109,7 @@ export class ReconnectingWebSocketTransport implements Transport {
|
|
|
109
109
|
this.onReconnect = options.onReconnect;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
async connect(): Promise<void> {
|
|
112
|
+
public async connect(): Promise<void> {
|
|
113
113
|
if (this.isClosed) {
|
|
114
114
|
throw new Error("Transport is closed");
|
|
115
115
|
}
|
|
@@ -136,7 +136,7 @@ export class ReconnectingWebSocketTransport implements Transport {
|
|
|
136
136
|
return this.isClosed || generation !== this.connectionGeneration;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
send(message: JSONRPCMessage): void {
|
|
139
|
+
public send(message: JSONRPCMessage): void {
|
|
140
140
|
if (this.isClosed) {
|
|
141
141
|
return;
|
|
142
142
|
}
|
|
@@ -162,14 +162,14 @@ export class ReconnectingWebSocketTransport implements Transport {
|
|
|
162
162
|
this.reconnect();
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
onMessage(handler: (message: JSONRPCMessage) => void): () => void {
|
|
165
|
+
public onMessage(handler: (message: JSONRPCMessage) => void): () => void {
|
|
166
166
|
this.messageHandlers.add(handler);
|
|
167
167
|
return () => {
|
|
168
168
|
this.messageHandlers.delete(handler);
|
|
169
169
|
};
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
close(): void {
|
|
172
|
+
public close(): void {
|
|
173
173
|
if (this.isClosed) {
|
|
174
174
|
return;
|
|
175
175
|
}
|
|
@@ -22,7 +22,7 @@ export class CachingRequestRegistry<REQ, RES> {
|
|
|
22
22
|
private toKey: ToKey<REQ>;
|
|
23
23
|
private cache: LRUCache<string, Promise<RES>>;
|
|
24
24
|
|
|
25
|
-
static jsonStringifySortKeys<T>(): ToKey<T> {
|
|
25
|
+
public static jsonStringifySortKeys<T>(): ToKey<T> {
|
|
26
26
|
return (o: T) => {
|
|
27
27
|
if (typeof o !== "object" || o === null) {
|
|
28
28
|
return String(o);
|
|
@@ -31,7 +31,7 @@ export class CachingRequestRegistry<REQ, RES> {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
constructor(
|
|
34
|
+
public constructor(
|
|
35
35
|
delegate: DeferredRequestRegistry<REQ, RES>,
|
|
36
36
|
options: CachingOptions<REQ> = {},
|
|
37
37
|
) {
|
|
@@ -27,7 +27,7 @@ export class DeferredRequestRegistry<REQ, RES> {
|
|
|
27
27
|
resolveExistingRequests?: () => RES;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
constructor(
|
|
30
|
+
public constructor(
|
|
31
31
|
operation: string,
|
|
32
32
|
makeRequest: (id: RequestId, req: REQ) => Promise<void>,
|
|
33
33
|
opts: {
|
|
@@ -42,7 +42,7 @@ export class DeferredRequestRegistry<REQ, RES> {
|
|
|
42
42
|
this.opts = opts;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
async request(opts: REQ): Promise<RES> {
|
|
45
|
+
public async request(opts: REQ): Promise<RES> {
|
|
46
46
|
if (this.opts.resolveExistingRequests) {
|
|
47
47
|
const result = this.opts.resolveExistingRequests();
|
|
48
48
|
for (const deferred of this.requests.values()) {
|
|
@@ -63,7 +63,7 @@ export class DeferredRequestRegistry<REQ, RES> {
|
|
|
63
63
|
return deferred.promise;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
resolve(requestId: RequestId, response: RES) {
|
|
66
|
+
public resolve(requestId: RequestId, response: RES) {
|
|
67
67
|
const entry = this.requests.get(requestId);
|
|
68
68
|
if (entry === undefined) {
|
|
69
69
|
return;
|
|
@@ -73,7 +73,7 @@ export class DeferredRequestRegistry<REQ, RES> {
|
|
|
73
73
|
this.requests.delete(requestId);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
reject(requestId: RequestId, error: Error) {
|
|
76
|
+
public reject(requestId: RequestId, error: Error) {
|
|
77
77
|
const entry = this.requests.get(requestId);
|
|
78
78
|
if (entry === undefined) {
|
|
79
79
|
return;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
4
|
+
import { withDevAssetUrl } from "../export-asset-url";
|
|
5
|
+
import type { ExportAsHTMLRequest } from "../types";
|
|
6
|
+
|
|
7
|
+
const REQUEST: ExportAsHTMLRequest = {
|
|
8
|
+
download: false,
|
|
9
|
+
includeCode: true,
|
|
10
|
+
files: [],
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
describe("withDevAssetUrl", () => {
|
|
14
|
+
const originalEnv = process.env.NODE_ENV;
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
process.env.NODE_ENV = originalEnv;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it.each(["development", "test"])(
|
|
21
|
+
"defaults assetUrl to the dev server in %s",
|
|
22
|
+
(env) => {
|
|
23
|
+
process.env.NODE_ENV = env;
|
|
24
|
+
expect(withDevAssetUrl(REQUEST).assetUrl).toBe(window.location.origin);
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
it("leaves a caller-provided assetUrl alone in dev", () => {
|
|
29
|
+
process.env.NODE_ENV = "development";
|
|
30
|
+
const cdn = "https://cdn.example.com/dist";
|
|
31
|
+
expect(withDevAssetUrl({ ...REQUEST, assetUrl: cdn }).assetUrl).toBe(cdn);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("does nothing in production", () => {
|
|
35
|
+
process.env.NODE_ENV = "production";
|
|
36
|
+
expect(withDevAssetUrl(REQUEST)).toBe(REQUEST);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("does not mutate the caller's request", () => {
|
|
40
|
+
process.env.NODE_ENV = "development";
|
|
41
|
+
const request = { ...REQUEST };
|
|
42
|
+
withDevAssetUrl(request);
|
|
43
|
+
expect(request.assetUrl).toBeUndefined();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { ExportAsHTMLRequest } from "./types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* In dev/test the CDN has no assets for the local build, so exports default
|
|
7
|
+
* to the dev server. Callers publishing to a remote origin pass their own
|
|
8
|
+
* assetUrl, which is left untouched.
|
|
9
|
+
*/
|
|
10
|
+
export function withDevAssetUrl(
|
|
11
|
+
request: ExportAsHTMLRequest,
|
|
12
|
+
): ExportAsHTMLRequest {
|
|
13
|
+
const isDevOrTest =
|
|
14
|
+
process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test";
|
|
15
|
+
if (request.assetUrl == null && isDevOrTest) {
|
|
16
|
+
return { ...request, assetUrl: window.location.origin };
|
|
17
|
+
}
|
|
18
|
+
return request;
|
|
19
|
+
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
waitForConnectionOpen,
|
|
12
12
|
waitForConnectionOpenIfNotebook,
|
|
13
13
|
} from "./connection";
|
|
14
|
+
import { withDevAssetUrl } from "./export-asset-url";
|
|
14
15
|
import type { EditRequests, RunRequests } from "./types";
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -430,15 +431,9 @@ export function createNetworkRequests(): EditRequests & RunRequests {
|
|
|
430
431
|
.then(handleResponse);
|
|
431
432
|
},
|
|
432
433
|
exportAsHTML: async (request) => {
|
|
433
|
-
if (
|
|
434
|
-
process.env.NODE_ENV === "development" ||
|
|
435
|
-
process.env.NODE_ENV === "test"
|
|
436
|
-
) {
|
|
437
|
-
request.assetUrl = window.location.origin;
|
|
438
|
-
}
|
|
439
434
|
return getClient()
|
|
440
435
|
.POST("/api/export/html", {
|
|
441
|
-
body: request,
|
|
436
|
+
body: withDevAssetUrl(request),
|
|
442
437
|
parseAs: "text",
|
|
443
438
|
params: getParams(),
|
|
444
439
|
})
|