@marimo-team/islands 0.25.3-dev13 → 0.25.3-dev134
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/{ConnectedDataExplorerComponent-CNMliVzw.js → ConnectedDataExplorerComponent-BAiSVaHj.js} +17 -15
- package/dist/{ErrorBoundary-BWYjMPq8.js → ErrorBoundary-CImYa86e.js} +16 -16
- package/dist/{abnfDiagram-VCTEODGH-D-Ez91gV.js → abnfDiagram-VCTEODGH-BBHN92-F.js} +2 -2
- package/dist/any-language-editor-3E24SnCV.js +75 -0
- package/dist/{architectureDiagram-5GKGNRK7-CTpcNtO8.js → architectureDiagram-5GKGNRK7-C0Rd64qh.js} +1 -1
- package/dist/{arrays-CN1djC4k.js → arrays-R6JsImiT.js} +8 -0
- package/dist/assets/__vite-browser-external-SaDYcg7x.js +1 -0
- package/dist/assets/{worker-HPY9fbhz.js → worker-CfME3uyi.js} +3 -2
- package/dist/{button-CvjkwdSk.js → button-BQRdqQi1.js} +199 -192
- package/dist/{capabilities-CQ5IjhzL.js → capabilities-CI4xUPsp.js} +2 -2
- package/dist/{chat-ui-DaHynk8g.js → chat-ui-DqcauBr6.js} +1142 -1141
- package/dist/{check-EDwSwP89.js → check-SpOMoCLP.js} +1 -1
- package/dist/{chunk-IMKFNOWR-DGxi-zeo.js → chunk-IMKFNOWR-hJXey5p2.js} +1 -1
- package/dist/{chunk-SHT3W25Y-CV9J3rAB.js → chunk-SHT3W25Y-JMzTmmVW.js} +1 -1
- package/dist/{chunk-SVP7TREG-BL-9Fqgg.js → chunk-SVP7TREG-BIyoHTXi.js} +1 -1
- package/dist/{chunk-TICWLB2K-Cu_XMjDM.js → chunk-TICWLB2K-CCzH2Uwa.js} +1 -1
- package/dist/{chunk-YOKDWASO-DPZk8vhS.js → chunk-YOKDWASO-DKplTrBY.js} +20 -20
- package/dist/{classDiagram-ZZMXUADV-BXXV13Hq.js → classDiagram-ZZMXUADV-DB1kOPS6.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-B7FXJ1Cp.js → classDiagram-v2-VYDZK3BY-CPaFL0OB.js} +2 -2
- package/dist/{code-visibility-O0uJkFyz.js → code-visibility-BZoWT5f1.js} +6 -6
- package/dist/{common-PAvTWnXO.js → common-CGmIoGK3.js} +6762 -6586
- package/dist/{copy-icon-Ft7t9bqz.js → copy-icon-C1trdvVt.js} +3 -3
- package/dist/{cynefinDiagram-5FMLGOSQ-CH96rwba.js → cynefinDiagram-5FMLGOSQ-CF93uliH.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-BD3PmpFY.js → dagre-GXQ25YYZ-BBB-_p9D.js} +1 -1
- package/dist/{data-grid-overlay-editor-DeWySy0K.js → data-grid-overlay-editor-CtOna148.js} +5 -5
- package/dist/{data-grid-types-BH_r7g7l.js → data-grid-types-0PJ-9N5n.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-BsSJJldB.js → diagram-S7CK7UJ4-BynCnbyt.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-muQtNZc4.js → diagram-UQ7AKVKN-svOxikMa.js} +1 -1
- package/dist/{diagram-VX7I27RA-CdhuZAqR.js → diagram-VX7I27RA-BVQSnPx0.js} +1 -1
- package/dist/{diagram-Z3DM3KII-BD8mXjOj.js → diagram-Z3DM3KII-nR1J0BH5.js} +1 -1
- package/dist/{dialog-CEBFZNyc.js → dialog-DgeqyrEZ.js} +3 -3
- package/dist/{dist-DpBpA_n1.js → dist-Lm-D-Vr6.js} +3 -3
- package/dist/{dropdown-menu-CP_qCYHC.js → dropdown-menu-CgL3fm8P.js} +1262 -1229
- package/dist/{ebnfDiagram-PWID7BFC-BIN1sweE.js → ebnfDiagram-PWID7BFC-C9tq9-Qo.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-Ctp5cfoN.js → erDiagram-RLTQ6QDP-BiaeB7Fi.js} +1 -1
- package/dist/{error-banner-D-WCIiMt.js → error-banner-D40pmfQQ.js} +83 -83
- package/dist/{error-entries-Bv6bZPF3.js → error-entries-CmFDQ9CA.js} +1018 -977
- package/dist/{feedback-button-WbN_sdUE.js → feedback-button-7ZbDdjfz.js} +23 -23
- package/dist/{flowDiagram-HODETNUW-3EfLrlFb.js → flowDiagram-HODETNUW-f-sLm87k.js} +2 -2
- package/dist/{formats-27CW_UP4.js → formats-CCxaLPNs.js} +3 -3
- package/dist/{ganttDiagram-EL5Y4UJY-BQT16JSc.js → ganttDiagram-EL5Y4UJY-Dh_4cBJZ.js} +2 -2
- package/dist/{glide-data-editor-_oc1WE9x.js → glide-data-editor-D72zmCan.js} +370 -368
- package/dist/{glide-utils-DJyqh0EM.js → glide-utils-Dz_F4pAY.js} +16 -16
- package/dist/{globals-C-fUiztk.js → globals-DE2S2io9.js} +3 -3
- package/dist/{highlighted-body-KPVGNVTW-zwjq21tf.js → highlighted-body-KPVGNVTW-3Ziwfz-V.js} +2 -2
- package/dist/{html-to-image-Bf8QMGi4.js → html-to-image-CfgsWObF.js} +630 -593
- package/dist/{infoDiagram-27XIBGKW-DipQoHxl.js → infoDiagram-27XIBGKW-BHrWywBe.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-DgFMjSGz.js → ishikawaDiagram-5VMMS53U-Da4s0Reg.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-w1ACVjgl.js → kanban-definition-UXKFOSKX-DDf1GVLj.js} +1 -1
- package/dist/{label-BDKF3Afc.js → label-D9_-zSg-.js} +3 -3
- package/dist/{linear-Cnaq3Tfb.js → linear-DWERh7eI.js} +121 -73
- package/dist/{loader-BQOfjTIJ.js → loader-DOVB0Syl.js} +12 -12
- package/dist/main.js +7441 -7115
- package/dist/{mermaid-CHo49Yv-.js → mermaid-ClwTt7OD.js} +38 -38
- package/dist/mermaid-HWGCJPDP-Du3-HPQA.js +2 -0
- package/dist/{mindmap-definition-YA3MSWOX-CrT0KSfX.js → mindmap-definition-YA3MSWOX-D60oMZtT.js} +1 -1
- package/dist/{number-overlay-editor-DdPkN3h1.js → number-overlay-editor-Br0lPnFE.js} +3 -3
- package/dist/{objects-CzUgoMIF.js → objects-DCmMvZtn.js} +1 -3
- package/dist/{once-BtE-wKRL.js → once-eG8Vmvjd.js} +1 -1
- package/dist/{pegDiagram-XKGWAZYB-BqvywNJA.js → pegDiagram-XKGWAZYB-B9wFpMga.js} +2 -2
- package/dist/{pieDiagram-E7YTZNPT-BfxJ_XCc.js → pieDiagram-E7YTZNPT-BZwvMuBT.js} +1 -1
- package/dist/precisionRound-DhAzeRcm.js +49 -0
- package/dist/{process-output-BZg56CCb.js → process-output-B64ZYWuI.js} +5 -5
- package/dist/{quadrantDiagram-AXDQQJYC-Ccw0xMjN.js → quadrantDiagram-AXDQQJYC-Blrm7V7_.js} +1 -1
- package/dist/{railroadDiagram-O6MQD6OU-8vFLVTPu.js → railroadDiagram-O6MQD6OU-DqhZGsN2.js} +2 -2
- package/dist/{react-vega-BED0DjiB.js → react-vega-C4T9Rb_w.js} +12284 -12270
- package/dist/{requirementDiagram-BXWQKSXE-a_MxGvxe.js → requirementDiagram-BXWQKSXE-CL5dYARV.js} +1 -1
- package/dist/{reveal-component-Bs9X_N3n.js → reveal-component-DnbPU885.js} +14 -14
- package/dist/{select-BButFi-d.js → select-CkF_GBxO.js} +537 -458
- package/dist/{sequenceDiagram-WJ2MYXX4-CXFMnxGh.js → sequenceDiagram-WJ2MYXX4-BSUCRd2x.js} +1 -1
- package/dist/{spec-BwarBpUJ.js → spec-DiSh_qJC.js} +41 -11
- package/dist/{stateDiagram-D77RDMKH-DQeA_CwI.js → stateDiagram-D77RDMKH-B-1Hvyb0.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-BxxQucm1.js → stateDiagram-v2-MP3YSRHH--HtiR4yB.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/{swimlanes-42K2YHIH-Do6L-cYg.js → swimlanes-42K2YHIH-Bdfhrd-2.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-DUzPvSVU.js → swimlanesDiagram-VR7AAH4N-BJIfxSz0.js} +2 -2
- package/dist/{swiper-component-DKntyn8p.js → swiper-component-BkCm-kLg.js} +2 -2
- package/dist/{time-BieKwdYS.js → time-BaMbJs4z.js} +18 -18
- package/dist/{timeline-definition-24CTP7MA-D3c46WM5.js → timeline-definition-24CTP7MA-Cs4EfFD3.js} +1 -1
- package/dist/{toDate-UtiwmnH2.js → toDate-DGvRHwTk.js} +19 -18
- package/dist/{toast-B9UMEi8u.js → toast-Btbve3Lm.js} +231 -213
- package/dist/{tooltip-BO1REbqs.js → tooltip-CJ4Rqasp.js} +327 -303
- package/dist/{use-restore-focus-BEM7sFWl.js → use-restore-focus-CmGwRNId.js} +1482 -1433
- package/dist/{useAsyncData-Deh5kszT.js → useAsyncData-BmVgnMP5.js} +15 -14
- package/dist/{useDateFormatter-D8XruNGG.js → useDateFormatter-eCL_SdeG.js} +2 -2
- package/dist/{useDeepCompareMemoize-DXwzRINI.js → useDeepCompareMemoize-Ch_AeGu6.js} +1 -1
- package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities-CSdWn6NP.js} +1 -1
- package/dist/{useLifecycle-j7D9uD1o.js → useLifecycle-B5BX921H.js} +3 -3
- package/dist/{useTheme-CRnjkku0.js → useTheme-Bq9pi_vz.js} +6 -6
- package/dist/{vega-component-BeF9dfyO.js → vega-component-BgyZ-COt.js} +147 -147
- package/dist/{vega-embed-container-BNHE18G9.js → vega-embed-container--LYZqguO.js} +2 -2
- package/dist/{vega-loader.browser-DNST6uVf.js → vega-loader.browser-B9Pz0EGP.js} +298 -507
- package/dist/{vennDiagram-4TSXK5OY-BSZD8JcZ.js → vennDiagram-4TSXK5OY-DRu0vRjO.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-48cUWkfA.js → wardleyDiagram-VM6X3IG4-DzbDxwDY.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-CswYgYeu.js → xychartDiagram-S5SC5T6Z-CLWj6ZFQ.js} +2 -2
- package/package.json +14 -14
- package/src/__tests__/host-imports.test.ts +1 -0
- package/src/__tests__/module-entry.test.ts +32 -0
- package/src/__tests__/mount.test.ts +0 -20
- package/src/components/ai/__tests__/ai-model-dropdown.test.tsx +63 -0
- package/src/components/ai/ai-model-dropdown.tsx +11 -6
- 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__/session-tabs.test.tsx +43 -0
- package/src/components/chat/acp/__tests__/state.test.ts +2 -2
- package/src/components/chat/acp/blocks.tsx +3 -2
- package/src/components/chat/acp/session-tabs.tsx +8 -3
- 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/TableTopBar.tsx +2 -0
- package/src/components/data-table/__tests__/chart-spec-model.test.ts +195 -195
- 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__/data-table.test.tsx +1 -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__/keyboard-selection.test.tsx +188 -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__/spec-snapshot.test.ts +20 -20
- package/src/components/data-table/charts/__tests__/spec.test.ts +78 -52
- package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
- package/src/components/data-table/charts/chart-spec/spec.ts +68 -43
- package/src/components/data-table/charts/charts.tsx +40 -30
- 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/chart-spec-model.tsx +23 -16
- 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 +13 -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/range-focus/atoms.ts +4 -6
- package/src/components/data-table/range-focus/use-cell-range-selection.ts +65 -38
- package/src/components/data-table/range-focus/use-scroll-into-view.ts +21 -2
- package/src/components/data-table/renderers.tsx +16 -7
- package/src/components/data-table/row-viewer-panel/__tests__/row-viewer.test.tsx +24 -1
- package/src/components/data-table/row-viewer-panel/row-viewer.tsx +4 -4
- 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 +48 -35
- package/src/components/debugger/debugger-code.tsx +5 -0
- package/src/components/dependency-graph/dependency-graph-tree.tsx +8 -8
- package/src/components/dependency-graph/elements.ts +24 -21
- package/src/components/dependency-graph/panels.tsx +2 -1
- package/src/components/dependency-graph/utils/__tests__/layout.test.ts +18 -11
- package/src/components/editor/SortableCell.tsx +1 -3
- package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
- package/src/components/editor/__tests__/package-alert.test.tsx +22 -1
- 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/collapse.tsx +5 -3
- 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/outline/__tests__/outline-keyboard.test.tsx +84 -0
- package/src/components/editor/chrome/panels/outline/floating-outline.tsx +39 -14
- package/src/components/editor/chrome/panels/outline/useActiveOutline.tsx +3 -2
- package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
- package/src/components/editor/chrome/panels/secrets-panel.tsx +1 -0
- package/src/components/editor/chrome/panels/snippets-panel.tsx +5 -1
- 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/__tests__/keyboard-shortcuts.test.tsx +61 -0
- package/src/components/editor/controls/command-palette.tsx +22 -10
- package/src/components/editor/controls/keyboard-shortcuts.tsx +14 -5
- 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__/file-explorer-navigation.test.tsx +47 -0
- package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
- package/src/components/editor/file-tree/file-explorer.tsx +45 -8
- package/src/components/editor/file-tree/file-name-input.tsx +1 -0
- 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/header/status.tsx +28 -5
- package/src/components/editor/links/__tests__/cell-link.test.tsx +146 -0
- package/src/components/editor/links/cell-link.tsx +14 -5
- package/src/components/editor/navigation/focus-utils.ts +4 -4
- package/src/components/editor/notebook-banner.tsx +1 -0
- package/src/components/editor/notebook-cell.tsx +11 -2
- package/src/components/editor/output/MarimoTracebackOutput.tsx +19 -17
- package/src/components/editor/output/__tests__/traceback.test.tsx +27 -1
- package/src/components/editor/output/ansi-reduce.ts +33 -20
- package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
- package/src/components/editor/package-alert.tsx +14 -7
- package/src/components/editor/renderers/grid-layout/grid-layout.tsx +12 -6
- 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.test.tsx +95 -0
- package/src/components/scratchpad/scratchpad.tsx +16 -3
- 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-keyboard.test.tsx +85 -0
- package/src/components/tracing/tracing.tsx +45 -41
- package/src/components/ui/__tests__/draggable-popover.test.tsx +24 -0
- package/src/components/ui/alert-dialog.tsx +1 -1
- package/src/components/ui/draggable-popover.tsx +24 -2
- package/src/core/ai/context/providers/datasource.ts +6 -2
- package/src/core/ai/tools/base.ts +16 -7
- package/src/core/ai/tools/edit-notebook-tool.ts +47 -45
- package/src/core/ai/tools/registry.ts +13 -8
- package/src/core/ai/tools/sample-tool.ts +6 -7
- 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/__tests__/session.test.ts +117 -36
- 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 +29 -29
- package/src/core/codemirror/cells/__tests__/extensions.test.ts +15 -0
- package/src/core/codemirror/cells/extensions.ts +7 -3
- package/src/core/codemirror/keymaps/vim.ts +2 -6
- package/src/core/codemirror/language/__tests__/sql.test.ts +22 -20
- package/src/core/codemirror/language/extension.ts +8 -8
- package/src/core/codemirror/language/languages/markdown.ts +8 -6
- package/src/core/codemirror/language/languages/python.ts +16 -10
- package/src/core/codemirror/language/languages/sql/sql.ts +9 -7
- package/src/core/codemirror/language/panel/panel.tsx +1 -0
- package/src/core/codemirror/language/types.ts +7 -7
- package/src/core/codemirror/rtc/extension.ts +24 -12
- package/src/core/codemirror/rtc/loro/awareness.ts +34 -19
- package/src/core/codemirror/rtc/loro/sync.ts +1 -0
- package/src/core/codemirror/utils.ts +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__/codemirror.test.ts +485 -0
- package/src/core/document/__tests__/draft.test.ts +322 -0
- package/src/core/document/__tests__/fixtures.ts +92 -0
- package/src/core/document/__tests__/hooks.test.tsx +386 -0
- package/src/core/document/__tests__/lifecycle-scenarios.test.ts +61 -0
- package/src/core/document/__tests__/model.test.ts +191 -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 +43 -0
- package/src/core/document/__tests__/scenarios.test.ts +205 -0
- package/src/core/document/__tests__/state.test.ts +438 -0
- package/src/core/document/__tests__/submission.test.ts +203 -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 +149 -0
- package/src/core/document/__tests__/user.ts +61 -0
- package/src/core/document/codemirror.ts +311 -0
- package/src/core/document/draft.ts +472 -0
- package/src/core/document/hooks.tsx +118 -0
- package/src/core/document/index.ts +15 -0
- package/src/core/document/model.ts +55 -0
- package/src/core/document/select.ts +41 -0
- package/src/core/document/state.ts +374 -0
- package/src/core/document/sync.ts +369 -0
- package/src/core/host/AGENTS.md +8 -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 +71 -0
- package/src/core/host/__tests__/replica.test.ts +550 -0
- package/src/core/host/__tests__/static.test.ts +169 -0
- package/src/core/host/__tests__/wasm.test.ts +162 -0
- package/src/core/host/backend.ts +19 -0
- package/src/core/host/connection.ts +1 -40
- package/src/core/host/index.ts +14 -0
- package/src/core/host/replica.ts +286 -0
- package/src/core/host/resources/attachments.ts +22 -0
- package/src/core/host/resources/document.ts +101 -0
- package/src/core/host/static.ts +72 -0
- package/src/core/host/wasm.ts +102 -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/islands/components/__tests__/IslandControls.test.tsx +7 -5
- 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/runtime/__tests__/createWsUrl.test.ts +77 -7
- package/src/core/runtime/__tests__/runtime.test.ts +32 -58
- package/src/core/runtime/runtime.ts +38 -42
- package/src/core/runtime/types.ts +0 -2
- package/src/core/state/__tests__/jotai.test.tsx +108 -0
- package/src/core/state/jotai.ts +1 -1
- package/src/core/static/__tests__/files.test.ts +38 -10
- package/src/core/static/files.ts +2 -0
- package/src/core/wasm/bridge.ts +38 -10
- package/src/core/wasm/store.ts +3 -3
- package/src/core/wasm/worker/bootstrap.ts +2 -1
- package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
- package/src/core/wasm/worker/types.ts +11 -0
- package/src/core/wasm/worker/worker.ts +48 -0
- package/src/core/websocket/__tests__/backend.test.ts +200 -0
- package/src/core/websocket/__tests__/connection-state.test.ts +0 -12
- package/src/core/websocket/__tests__/useNotebookReplica.hook.test.tsx +141 -0
- package/src/core/websocket/__tests__/useNotebookSession.hook.test.tsx +154 -428
- package/src/core/websocket/backend.ts +106 -0
- package/src/core/websocket/connection-state.ts +1 -11
- package/src/core/websocket/state.ts +7 -0
- package/src/core/websocket/transports/__tests__/notebook.test.ts +445 -0
- package/src/core/websocket/transports/notebook.ts +190 -0
- package/src/core/websocket/types.ts +2 -2
- package/src/core/websocket/useNotebookReplica.ts +69 -0
- package/src/core/websocket/useNotebookSession.tsx +10 -22
- 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 +6 -10
- package/src/hooks/useDeepCompareMemoize.ts +1 -1
- package/src/hooks/useDelayElapsed.ts +14 -10
- package/src/hooks/useElapsedTime.ts +3 -3
- package/src/hooks/useEventListener.ts +1 -2
- 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/mount.tsx +0 -4
- package/src/plugins/__tests__/plugin-object-schemas.test.ts +104 -0
- package/src/plugins/core/registerReactComponent.tsx +1 -1
- package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
- package/src/plugins/impl/DataEditorPlugin.tsx +1 -1
- package/src/plugins/impl/DataTablePlugin.tsx +29 -33
- 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 +14 -11
- 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/components/column-summary.tsx +4 -2
- package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
- package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -2
- package/src/plugins/impl/data-frames/__tests__/sidebar.test.tsx +160 -0
- package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +66 -66
- package/src/plugins/impl/data-frames/panel.tsx +43 -16
- 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/plotly/PlotlyPlugin.tsx +2 -5
- package/src/plugins/impl/plotly/selection.ts +25 -19
- package/src/plugins/impl/vega/VegaPlugin.tsx +2 -3
- 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/tracer.ts +9 -5
- package/src/utils/zod-utils.ts +1 -3
- package/dist/any-language-editor-DZ_YTVus.js +0 -76
- package/dist/assets/__vite-browser-external-BXYkiFsk.js +0 -1
- package/dist/mermaid-HWGCJPDP-ByCsZnYF.js +0 -2
- package/dist/precisionRound-DMIVQrt2.js +0 -97
- package/src/core/host/__tests__/events.test.ts +0 -46
- package/src/core/host/__tests__/notebook-client.test.ts +0 -191
- package/src/core/host/__tests__/protocol.test.ts +0 -214
- package/src/core/host/events.ts +0 -60
- package/src/core/host/notebook-client.ts +0 -226
- package/src/core/host/protocol.ts +0 -1943
- package/src/core/state/__tests__/jotai.test.ts +0 -25
- package/src/core/websocket/__tests__/createConnectionTransport.test.ts +0 -49
- package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +0 -140
- package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
- package/src/core/websocket/transports/__tests__/sse.test.ts +0 -335
- 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/create.ts +0 -31
- package/src/core/websocket/transports/sse.ts +0 -308
- package/src/core/websocket/transports/transport.ts +0 -50
- package/src/core/websocket/transports/ws.ts +0 -98
- package/src/core/websocket/useNotebookClient.ts +0 -80
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type NotebookTransport,
|
|
5
|
+
sse,
|
|
6
|
+
StreamStopped,
|
|
7
|
+
type StreamUrl,
|
|
8
|
+
} from "@marimo-team/client";
|
|
9
|
+
import { jsonParseWithSpecialChar } from "@/utils/json/json-parser";
|
|
10
|
+
import { classifyCloseEvent } from "../connection-state";
|
|
11
|
+
|
|
12
|
+
/** The server can be slow to accept when many notebooks are open. */
|
|
13
|
+
const CONNECT_MS = 10_000;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The error that ends a stream on this close, or none to retry.
|
|
17
|
+
*
|
|
18
|
+
* `cause` is the connection status the editor shows.
|
|
19
|
+
*/
|
|
20
|
+
function stopOn(close: { code?: number; reason?: string }) {
|
|
21
|
+
const decision = classifyCloseEvent(close);
|
|
22
|
+
if (decision.kind === "retry") {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
const reason = "reason" in decision.status ? decision.status.reason : "";
|
|
26
|
+
return new StreamStopped(reason, { cause: decision.status });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Read a URL provider, stopping the stream when it gives up. */
|
|
30
|
+
async function resolve(url: StreamUrl): Promise<URL | undefined> {
|
|
31
|
+
try {
|
|
32
|
+
return new URL(typeof url === "function" ? await url() : url);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
if (error instanceof StreamStopped) {
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Send each request with the runtime's headers, read before each request. */
|
|
42
|
+
export function withHeaders(
|
|
43
|
+
headers: () => Record<string, string>,
|
|
44
|
+
): typeof globalThis.fetch {
|
|
45
|
+
return (input, init) => {
|
|
46
|
+
const sent = new Headers(init?.headers);
|
|
47
|
+
for (const [name, value] of Object.entries(headers())) {
|
|
48
|
+
sent.set(name, value);
|
|
49
|
+
}
|
|
50
|
+
return globalThis.fetch(input, { ...init, headers: sent });
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A resume cursor and replica belong to the session that produced them. */
|
|
55
|
+
function withSessionIdentity(
|
|
56
|
+
url: StreamUrl,
|
|
57
|
+
connect: (target: URL) => NotebookTransport,
|
|
58
|
+
): NotebookTransport {
|
|
59
|
+
let previous: string | undefined;
|
|
60
|
+
return async (attempt, handlers) => {
|
|
61
|
+
const target = await resolve(url);
|
|
62
|
+
if (target === undefined || attempt.signal.aborted) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
target.searchParams.delete("cursor");
|
|
66
|
+
const identity = new URL(target);
|
|
67
|
+
identity.searchParams.delete("access_token");
|
|
68
|
+
identity.searchParams.sort();
|
|
69
|
+
identity.hash = "";
|
|
70
|
+
const key = identity.toString();
|
|
71
|
+
let { cursor } = attempt;
|
|
72
|
+
if (previous !== undefined && previous !== key) {
|
|
73
|
+
// Reset both the stream's cursor and its subscribers' state.
|
|
74
|
+
handlers.message({ event: "reset", data: {} });
|
|
75
|
+
cursor = null;
|
|
76
|
+
}
|
|
77
|
+
previous = key;
|
|
78
|
+
if (!attempt.signal.aborted) {
|
|
79
|
+
return connect(target)({ ...attempt, cursor }, handlers);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Follow a marimo session over its WebSocket route.
|
|
86
|
+
*
|
|
87
|
+
* Each attempt opens one socket with the cursor as a query parameter. The
|
|
88
|
+
* editor's close policy decides which closes end the stream.
|
|
89
|
+
*
|
|
90
|
+
* TODO: Notice a connection that died without closing, such as after a laptop
|
|
91
|
+
* sleeps. `/sse` sends a heartbeat that its silence limit relies on, and
|
|
92
|
+
* `/ws` drops it. Sending it on `/ws` would let this transport close a
|
|
93
|
+
* silent socket.
|
|
94
|
+
*/
|
|
95
|
+
export function websocket(url: StreamUrl): NotebookTransport {
|
|
96
|
+
return withSessionIdentity(
|
|
97
|
+
url,
|
|
98
|
+
(target) =>
|
|
99
|
+
async ({ cursor, signal }, { open, message }) => {
|
|
100
|
+
if (cursor !== null) {
|
|
101
|
+
target.searchParams.set("cursor", cursor);
|
|
102
|
+
}
|
|
103
|
+
await new Promise<void>((settle, reject) => {
|
|
104
|
+
const socket = new WebSocket(target);
|
|
105
|
+
const timeout = setTimeout(() => socket.close(), CONNECT_MS);
|
|
106
|
+
const abort = () => socket.close();
|
|
107
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
108
|
+
socket.addEventListener("open", () => {
|
|
109
|
+
clearTimeout(timeout);
|
|
110
|
+
open();
|
|
111
|
+
});
|
|
112
|
+
socket.addEventListener("message", ({ data }) =>
|
|
113
|
+
// marimo's parser reads `NaN`. A message it cannot read arrives as an
|
|
114
|
+
// empty object, which `NotebookEvents` rejects as not an event.
|
|
115
|
+
message(jsonParseWithSpecialChar(String(data))),
|
|
116
|
+
);
|
|
117
|
+
socket.addEventListener("close", (event) => {
|
|
118
|
+
clearTimeout(timeout);
|
|
119
|
+
signal.removeEventListener("abort", abort);
|
|
120
|
+
const stopped = signal.aborted ? undefined : stopOn(event);
|
|
121
|
+
if (stopped === undefined) {
|
|
122
|
+
settle();
|
|
123
|
+
} else {
|
|
124
|
+
reject(stopped);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Follow a marimo session over its server-sent events route.
|
|
134
|
+
*
|
|
135
|
+
* The session's `close` frames carry the same reasons as WebSocket closes.
|
|
136
|
+
* marimo's parser decodes tagged bigints and non-finite numbers in every frame.
|
|
137
|
+
*/
|
|
138
|
+
export function sessionSse(
|
|
139
|
+
url: StreamUrl,
|
|
140
|
+
headers: () => Record<string, string>,
|
|
141
|
+
): NotebookTransport {
|
|
142
|
+
return withSessionIdentity(url, (target) =>
|
|
143
|
+
sse(target, {
|
|
144
|
+
parseData: jsonParseWithSpecialChar,
|
|
145
|
+
fetch: withHeaders(headers),
|
|
146
|
+
stop: (frame) =>
|
|
147
|
+
frame.event === "close"
|
|
148
|
+
? stopOn(frame.data as { code?: number; reason?: string })
|
|
149
|
+
: undefined,
|
|
150
|
+
}),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** A connection that opens and delivers nothing, for static notebooks. */
|
|
155
|
+
export const idle: NotebookTransport = ({ signal }, { open }) =>
|
|
156
|
+
new Promise((settle) => {
|
|
157
|
+
open();
|
|
158
|
+
signal.addEventListener("abort", () => settle(), { once: true });
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Follow the notebook running in the WASM worker.
|
|
163
|
+
*
|
|
164
|
+
* The worker sends marimo notifications as `{ data: notification }`, which
|
|
165
|
+
* arrive as `marimo` events.
|
|
166
|
+
*/
|
|
167
|
+
export function pyodide(
|
|
168
|
+
attach: (consumer: (message: MessageEvent<string>) => void) => void,
|
|
169
|
+
): NotebookTransport {
|
|
170
|
+
return ({ signal }, { open, message }) =>
|
|
171
|
+
new Promise((settle) => {
|
|
172
|
+
open();
|
|
173
|
+
attach(({ data }) => {
|
|
174
|
+
if (signal.aborted) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const value = jsonParseWithSpecialChar(data);
|
|
178
|
+
// The worker sends notifications without the host's event envelope.
|
|
179
|
+
message(
|
|
180
|
+
typeof value === "object" &&
|
|
181
|
+
value !== null &&
|
|
182
|
+
!("event" in value) &&
|
|
183
|
+
"data" in value
|
|
184
|
+
? { event: "marimo", data: { marimo: value.data } }
|
|
185
|
+
: value,
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
signal.addEventListener("abort", () => settle(), { once: true });
|
|
189
|
+
});
|
|
190
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import type { ConnectionStatus as HostConnectionStatus } from "../host
|
|
3
|
+
import type { ConnectionStatus as HostConnectionStatus } from "../host";
|
|
4
4
|
import type { NotificationMessageData } from "../kernel/messages";
|
|
5
5
|
|
|
6
6
|
export {
|
|
7
7
|
ConnectionState,
|
|
8
8
|
ConnectionClosedReason as WebSocketClosedReason,
|
|
9
|
-
} from "../host
|
|
9
|
+
} from "../host";
|
|
10
10
|
|
|
11
11
|
export type ConnectionPhase =
|
|
12
12
|
| NotificationMessageData<"startup-progress">["phase"]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
/** The editor session's replica, following its backend while mounted. */
|
|
4
|
+
|
|
5
|
+
import { useStore } from "jotai";
|
|
6
|
+
import { useEffect, useMemo } from "react";
|
|
7
|
+
import useEvent from "react-use-event-hook";
|
|
8
|
+
import type {
|
|
9
|
+
NotificationMessage,
|
|
10
|
+
NotificationPayload,
|
|
11
|
+
} from "../kernel/messages";
|
|
12
|
+
import { createNotebookReplica, type NotebookReplica } from "../host";
|
|
13
|
+
import { notebookReplicaAtom } from "./state";
|
|
14
|
+
import { createEditorBackend, type EditorBackendOptions } from "./backend";
|
|
15
|
+
|
|
16
|
+
type Options = EditorBackendOptions & {
|
|
17
|
+
onNotification: (notification: NotificationPayload) => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function useNotebookReplica(options: Options): NotebookReplica {
|
|
21
|
+
const store = useStore();
|
|
22
|
+
const { static: isStatic, transportType } = options;
|
|
23
|
+
const replica = useMemo(() => createNotebookReplica(store), [store]);
|
|
24
|
+
const url = useEvent(options.url);
|
|
25
|
+
const headers = useEvent(options.headers);
|
|
26
|
+
const notebookUrl = useEvent(options.notebookUrl);
|
|
27
|
+
const waitUntilReady = useEvent(options.waitUntilReady);
|
|
28
|
+
const checkHealth = useEvent(options.checkHealth);
|
|
29
|
+
const onNotification = useEvent(options.onNotification);
|
|
30
|
+
// Opens nothing until the replica follows it.
|
|
31
|
+
const backend = useMemo(
|
|
32
|
+
() =>
|
|
33
|
+
createEditorBackend({
|
|
34
|
+
static: isStatic,
|
|
35
|
+
transportType,
|
|
36
|
+
url,
|
|
37
|
+
headers,
|
|
38
|
+
notebookUrl,
|
|
39
|
+
waitUntilReady,
|
|
40
|
+
checkHealth,
|
|
41
|
+
}),
|
|
42
|
+
[
|
|
43
|
+
isStatic,
|
|
44
|
+
transportType,
|
|
45
|
+
url,
|
|
46
|
+
headers,
|
|
47
|
+
notebookUrl,
|
|
48
|
+
waitUntilReady,
|
|
49
|
+
checkHealth,
|
|
50
|
+
],
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
store.set(notebookReplicaAtom, replica);
|
|
55
|
+
const disconnect = replica.connect(backend, {
|
|
56
|
+
// The host validates the envelope, not the native notification contents.
|
|
57
|
+
onNotification: (notification) =>
|
|
58
|
+
onNotification({ data: notification as NotificationMessage }),
|
|
59
|
+
});
|
|
60
|
+
return () => {
|
|
61
|
+
disconnect();
|
|
62
|
+
if (store.get(notebookReplicaAtom) === replica) {
|
|
63
|
+
store.set(notebookReplicaAtom, null);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}, [replica, backend, store, onNotification]);
|
|
67
|
+
|
|
68
|
+
return replica;
|
|
69
|
+
}
|
|
@@ -5,7 +5,6 @@ import { useAtom, useAtomValue, useSetAtom, useStore } from "jotai";
|
|
|
5
5
|
import { useLayoutEffect } from "react";
|
|
6
6
|
import { useErrorBoundary } from "react-error-boundary";
|
|
7
7
|
import { toast } from "@/components/ui/use-toast";
|
|
8
|
-
import { attachmentsAtom } from "@/core/attachments/state";
|
|
9
8
|
import { getNotebook, useCellActions } from "@/core/cells/cells";
|
|
10
9
|
import { applyTransactionChanges } from "@/core/cells/document-changes";
|
|
11
10
|
import { AUTOCOMPLETER } from "@/core/codemirror/completion/Autocompleter";
|
|
@@ -13,7 +12,7 @@ import type {
|
|
|
13
12
|
NotificationMessageData,
|
|
14
13
|
NotificationPayload,
|
|
15
14
|
} from "@/core/kernel/messages";
|
|
16
|
-
import {
|
|
15
|
+
import { useNotebookReplica } from "@/core/websocket/useNotebookReplica";
|
|
17
16
|
import { renderHTML } from "@/plugins/core/RenderHTML";
|
|
18
17
|
import {
|
|
19
18
|
handleWidgetMessage,
|
|
@@ -66,7 +65,7 @@ import { type LayoutState, useLayoutActions } from "../layout/state";
|
|
|
66
65
|
import { kioskModeAtom } from "../mode";
|
|
67
66
|
import { connectionAtom, startupProgressAtom } from "../network/connection";
|
|
68
67
|
import type { RequestId } from "../network/DeferredRequestRegistry";
|
|
69
|
-
import {
|
|
68
|
+
import { useRuntimeManager } from "../runtime/config";
|
|
70
69
|
import { SECRETS_REGISTRY } from "../secrets/request-registry";
|
|
71
70
|
import { isStaticNotebook } from "../static/static-state";
|
|
72
71
|
import {
|
|
@@ -76,7 +75,6 @@ import {
|
|
|
76
75
|
import { useStorageActions } from "../storage/state";
|
|
77
76
|
import { useVariablesActions } from "../variables/state";
|
|
78
77
|
import type { VariableName } from "../variables/types";
|
|
79
|
-
import { isWasm } from "../wasm/utils";
|
|
80
78
|
import { WebSocketClosedReason, ConnectionState } from "./types";
|
|
81
79
|
|
|
82
80
|
const SUPPORTS_LAZY_KERNELS = true;
|
|
@@ -136,11 +134,9 @@ export function useNotebookSession(opts: {
|
|
|
136
134
|
const setStableSessionId = useSetAtom(stableSessionIdAtom);
|
|
137
135
|
const setCapabilities = useSetAtom(capabilitiesAtom);
|
|
138
136
|
const runtimeManager = useRuntimeManager();
|
|
139
|
-
const { notebookId } = useAtomValue(runtimeConfigAtom);
|
|
140
137
|
const transportType = useAtomValue(connectionTransportTypeAtom);
|
|
141
138
|
const setCacheInfo = useSetAtom(cacheInfoAtom);
|
|
142
139
|
const setKernelStartupError = useSetAtom(kernelStartupErrorAtom);
|
|
143
|
-
const setAttachments = useSetAtom(attachmentsAtom);
|
|
144
140
|
const {
|
|
145
141
|
setNamespaces: setStorageNamespaces,
|
|
146
142
|
filterFromVariables: filterStorageFromVariables,
|
|
@@ -148,9 +144,6 @@ export function useNotebookSession(opts: {
|
|
|
148
144
|
|
|
149
145
|
const handleNotification = (msg: NotificationPayload) => {
|
|
150
146
|
switch (msg.data.op) {
|
|
151
|
-
case "attachments":
|
|
152
|
-
setAttachments(msg.data.attachments);
|
|
153
|
-
return;
|
|
154
147
|
case "reload":
|
|
155
148
|
reloadSafe();
|
|
156
149
|
return;
|
|
@@ -185,7 +178,6 @@ export function useNotebookSession(opts: {
|
|
|
185
178
|
existingCells,
|
|
186
179
|
});
|
|
187
180
|
if (!msg.data.resumed) {
|
|
188
|
-
setAttachments([]);
|
|
189
181
|
// A freshly started kernel may expose a different environment
|
|
190
182
|
// (e.g. new env vars); re-run discovery instead of reusing stale
|
|
191
183
|
// suggestions from the previous kernel instance.
|
|
@@ -416,25 +408,23 @@ export function useNotebookSession(opts: {
|
|
|
416
408
|
variant: "danger",
|
|
417
409
|
});
|
|
418
410
|
};
|
|
419
|
-
const
|
|
420
|
-
sessionId,
|
|
411
|
+
const replica = useNotebookReplica({
|
|
421
412
|
static: isStaticNotebook(),
|
|
422
413
|
transportType,
|
|
423
|
-
messageFormat:
|
|
424
|
-
notebookId && transportType === "websocket" ? "host" : "session",
|
|
425
414
|
url: () =>
|
|
426
415
|
transportType === "sse"
|
|
427
416
|
? runtimeManager.getSseURL(sessionId).toString()
|
|
428
417
|
: runtimeManager.getWsURL(sessionId).toString(),
|
|
429
418
|
// SSE uses headers for auth. WebSockets carry the token in the URL.
|
|
430
419
|
headers: () => runtimeManager.headers(),
|
|
420
|
+
notebookUrl: (notebookId) => runtimeManager.getNotebookURL(notebookId),
|
|
421
|
+
// Only the host's transports read the URL, so static and WASM never wait.
|
|
431
422
|
waitUntilReady: async () => {
|
|
432
|
-
if (!
|
|
423
|
+
if (!runtimeManager.isSameOrigin) {
|
|
433
424
|
await runtimeManager.waitForHealthy();
|
|
434
425
|
}
|
|
435
426
|
},
|
|
436
427
|
checkHealth: () => runtimeManager.reconcileFromHealth(),
|
|
437
|
-
onMessageError,
|
|
438
428
|
onNotification: (notification) => {
|
|
439
429
|
try {
|
|
440
430
|
handleNotification(notification);
|
|
@@ -448,11 +438,10 @@ export function useNotebookSession(opts: {
|
|
|
448
438
|
// Subscribe before passive connection setup: static/WASM transports can emit synchronously.
|
|
449
439
|
useLayoutEffect(() => {
|
|
450
440
|
const updateConnection = () => {
|
|
451
|
-
const status = store.get(
|
|
441
|
+
const status = store.get(replica.connectionAtom);
|
|
452
442
|
if (status.state === ConnectionState.NOT_STARTED) {
|
|
453
443
|
return;
|
|
454
444
|
}
|
|
455
|
-
setAttachments([]);
|
|
456
445
|
if (status.state === ConnectionState.OPEN) {
|
|
457
446
|
updateStartupProgress(null);
|
|
458
447
|
return; // kernel-ready, not the socket, establishes session readiness.
|
|
@@ -480,17 +469,16 @@ export function useNotebookSession(opts: {
|
|
|
480
469
|
return status;
|
|
481
470
|
});
|
|
482
471
|
};
|
|
483
|
-
const unsubscribe = store.sub(
|
|
472
|
+
const unsubscribe = store.sub(replica.connectionAtom, updateConnection);
|
|
484
473
|
updateConnection();
|
|
485
474
|
return unsubscribe;
|
|
486
475
|
}, [
|
|
487
|
-
|
|
476
|
+
replica,
|
|
488
477
|
store,
|
|
489
478
|
setConnection,
|
|
490
|
-
setAttachments,
|
|
491
479
|
setKernelStartupError,
|
|
492
480
|
updateStartupProgress,
|
|
493
481
|
]);
|
|
494
482
|
|
|
495
|
-
return {
|
|
483
|
+
return { replica, connection, reconnect: replica.reconnect };
|
|
496
484
|
}
|
package/src/css/globals.css
CHANGED
|
@@ -75,6 +75,9 @@
|
|
|
75
75
|
--ring: hsl(215deg 20.2% 65.1%);
|
|
76
76
|
|
|
77
77
|
/* Semantic colors. */
|
|
78
|
+
--brand: light-dark(#365f43, #a8c9ae);
|
|
79
|
+
--brand-hover: light-dark(#2b5037, #bdd8c2);
|
|
80
|
+
--brand-foreground: light-dark(#fff, #172b1c);
|
|
78
81
|
--destructive: var(--red-9, #e5484d);
|
|
79
82
|
--destructive-hover: var(--red-10, #dc3e42);
|
|
80
83
|
--destructive-border: var(--red-11, #ce2c31);
|
|
@@ -89,6 +92,7 @@
|
|
|
89
92
|
--action-hover: var(--yellow-10, #ffdc00);
|
|
90
93
|
--action-border: var(--yellow-11, #9e6c00);
|
|
91
94
|
--action-foreground: var(--yellow-12, #473b1f);
|
|
95
|
+
--search-highlight: var(--yellow-4, #fff394);
|
|
92
96
|
--link: light-dark(hsl(211deg 90% 42%), hsl(211deg 90% 62%));
|
|
93
97
|
--link-visited: light-dark(hsl(272deg 51% 54%), hsl(272deg 51% 74%));
|
|
94
98
|
--stale: hsl(42deg 56% 44% / 25%);
|
|
@@ -137,6 +141,7 @@
|
|
|
137
141
|
--action-hover: var(--yellow-7, #665417);
|
|
138
142
|
--action-border: var(--yellow-11, #f5e147);
|
|
139
143
|
--action-foreground: var(--yellow-12, #f6eeb4);
|
|
144
|
+
--search-highlight: var(--yellow-6, #524202);
|
|
140
145
|
--stale: hsl(54deg 100% 86.7% / 25%);
|
|
141
146
|
}
|
|
142
147
|
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import { act, render, renderHook, waitFor } from "@testing-library/react";
|
|
3
|
+
import { Activity } from "react";
|
|
4
|
+
import { describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { Deferred } from "@/utils/Deferred";
|
|
6
|
+
import { useAsyncData } from "../useAsyncData";
|
|
7
|
+
|
|
8
|
+
function AsyncDataStatus({ fetch }: { fetch: () => Promise<string> }) {
|
|
9
|
+
const result = useAsyncData(fetch, []);
|
|
10
|
+
return (
|
|
11
|
+
<output
|
|
12
|
+
data-status={result.status}
|
|
13
|
+
data-fetching={result.isFetching}
|
|
14
|
+
data-error={result.error?.message ?? ""}
|
|
15
|
+
>
|
|
16
|
+
{result.data}
|
|
17
|
+
</output>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe("useAsyncData", () => {
|
|
22
|
+
it.each(["success", "error"] as const)(
|
|
23
|
+
"restarts fetching when Activity reopens after %s",
|
|
24
|
+
async (initialStatus) => {
|
|
25
|
+
const initial = new Deferred<string>();
|
|
26
|
+
const restarted = new Deferred<string>();
|
|
27
|
+
const fetch = vi
|
|
28
|
+
.fn(() => restarted.promise)
|
|
29
|
+
.mockImplementationOnce(() => initial.promise);
|
|
30
|
+
const view = render(
|
|
31
|
+
<Activity mode="visible">
|
|
32
|
+
<AsyncDataStatus fetch={fetch} />
|
|
33
|
+
</Activity>,
|
|
34
|
+
);
|
|
35
|
+
await act(async () => {
|
|
36
|
+
if (initialStatus === "success") {
|
|
37
|
+
initial.resolve("first");
|
|
38
|
+
} else {
|
|
39
|
+
initial.reject(new Error("offline"));
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
expect(view.getByRole("status").dataset.status).toBe(initialStatus);
|
|
43
|
+
|
|
44
|
+
view.rerender(
|
|
45
|
+
<Activity mode="hidden">
|
|
46
|
+
<AsyncDataStatus fetch={fetch} />
|
|
47
|
+
</Activity>,
|
|
48
|
+
);
|
|
49
|
+
view.rerender(
|
|
50
|
+
<Activity mode="visible">
|
|
51
|
+
<AsyncDataStatus fetch={fetch} />
|
|
52
|
+
</Activity>,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
expect(fetch).toHaveBeenCalledTimes(2);
|
|
56
|
+
expect(view.getByRole("status").dataset).toMatchObject({
|
|
57
|
+
status: initialStatus === "success" ? "loading" : "pending",
|
|
58
|
+
error: "",
|
|
59
|
+
fetching: "true",
|
|
60
|
+
});
|
|
61
|
+
expect(view.getByRole("status").textContent).toBe(
|
|
62
|
+
initialStatus === "success" ? "first" : "",
|
|
63
|
+
);
|
|
64
|
+
await act(async () => restarted.resolve("latest"));
|
|
65
|
+
expect(view.getByRole("status").dataset).toMatchObject({
|
|
66
|
+
status: "success",
|
|
67
|
+
fetching: "false",
|
|
68
|
+
});
|
|
69
|
+
expect(view.getByRole("status").textContent).toBe("latest");
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
it("ignores a request canceled when Activity hides", async () => {
|
|
74
|
+
const canceled = new Deferred<string>();
|
|
75
|
+
const restarted = new Deferred<string>();
|
|
76
|
+
const fetch = vi
|
|
77
|
+
.fn(() => restarted.promise)
|
|
78
|
+
.mockImplementationOnce(() => canceled.promise);
|
|
79
|
+
const view = render(
|
|
80
|
+
<Activity mode="visible">
|
|
81
|
+
<AsyncDataStatus fetch={fetch} />
|
|
82
|
+
</Activity>,
|
|
83
|
+
);
|
|
84
|
+
view.rerender(
|
|
85
|
+
<Activity mode="hidden">
|
|
86
|
+
<AsyncDataStatus fetch={fetch} />
|
|
87
|
+
</Activity>,
|
|
88
|
+
);
|
|
89
|
+
view.rerender(
|
|
90
|
+
<Activity mode="visible">
|
|
91
|
+
<AsyncDataStatus fetch={fetch} />
|
|
92
|
+
</Activity>,
|
|
93
|
+
);
|
|
94
|
+
expect(fetch).toHaveBeenCalledTimes(2);
|
|
95
|
+
await act(async () => canceled.resolve("obsolete"));
|
|
96
|
+
expect(view.getByRole("status").dataset.status).toBe("pending");
|
|
97
|
+
expect(view.getByRole("status").textContent).toBe("");
|
|
98
|
+
await act(async () => restarted.resolve("latest"));
|
|
99
|
+
expect(view.getByRole("status").dataset.status).toBe("success");
|
|
100
|
+
expect(view.getByRole("status").textContent).toBe("latest");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("preserves data while refetching and ignores obsolete requests", async () => {
|
|
104
|
+
const requests = [
|
|
105
|
+
new Deferred<string>(),
|
|
106
|
+
new Deferred<string>(),
|
|
107
|
+
new Deferred<string>(),
|
|
108
|
+
];
|
|
109
|
+
const fetch = vi
|
|
110
|
+
.fn(() => requests[0].promise)
|
|
111
|
+
.mockImplementationOnce(() => requests[0].promise)
|
|
112
|
+
.mockImplementationOnce(() => requests[1].promise)
|
|
113
|
+
.mockImplementationOnce(() => requests[2].promise);
|
|
114
|
+
const { result, rerender } = renderHook(
|
|
115
|
+
({ key }) => useAsyncData(fetch, [key]),
|
|
116
|
+
{ initialProps: { key: 0 } },
|
|
117
|
+
);
|
|
118
|
+
expect(result.current.status).toBe("pending");
|
|
119
|
+
await act(async () => requests[0].resolve("first"));
|
|
120
|
+
expect(result.current.data).toBe("first");
|
|
121
|
+
rerender({ key: 1 });
|
|
122
|
+
expect(result.current).toMatchObject({ status: "loading", data: "first" });
|
|
123
|
+
rerender({ key: 2 });
|
|
124
|
+
await act(async () => requests[1].resolve("obsolete"));
|
|
125
|
+
expect(result.current).toMatchObject({ status: "loading", data: "first" });
|
|
126
|
+
await act(async () => requests[2].resolve("latest"));
|
|
127
|
+
expect(result.current).toMatchObject({ status: "success", data: "latest" });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("retries after errors and allows manual updates during a request", async () => {
|
|
131
|
+
const request = new Deferred<string>();
|
|
132
|
+
const fetch = vi
|
|
133
|
+
.fn<() => Promise<string>>()
|
|
134
|
+
.mockRejectedValueOnce(new Error("offline"))
|
|
135
|
+
.mockImplementation(() => request.promise);
|
|
136
|
+
const { result } = renderHook(() => useAsyncData(fetch, []));
|
|
137
|
+
await waitFor(() => expect(result.current.status).toBe("error"));
|
|
138
|
+
act(() => result.current.refetch());
|
|
139
|
+
expect(result.current.status).toBe("pending");
|
|
140
|
+
act(() => result.current.setData("manual"));
|
|
141
|
+
expect(result.current).toMatchObject({ status: "success", data: "manual" });
|
|
142
|
+
await act(async () => request.resolve("response"));
|
|
143
|
+
expect(result.current).toMatchObject({
|
|
144
|
+
status: "success",
|
|
145
|
+
data: "response",
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -121,6 +121,57 @@ describe("useDebounceControlledState", () => {
|
|
|
121
121
|
|
|
122
122
|
expect(onChange).not.toHaveBeenCalled();
|
|
123
123
|
});
|
|
124
|
+
|
|
125
|
+
it("replaces an uncommitted draft when the external value changes", () => {
|
|
126
|
+
const onChange = vi.fn();
|
|
127
|
+
const { result, rerender } = renderHook(
|
|
128
|
+
({ value }) =>
|
|
129
|
+
useDebounceControlledState({
|
|
130
|
+
initialValue: value,
|
|
131
|
+
onChange,
|
|
132
|
+
delay: 1000,
|
|
133
|
+
}),
|
|
134
|
+
{ initialProps: { value: "initial" } },
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
act(() => result.current.onChange("draft"));
|
|
138
|
+
rerender({ value: "external" });
|
|
139
|
+
expect(result.current.value).toBe("external");
|
|
140
|
+
act(() => vi.runAllTimers());
|
|
141
|
+
expect(result.current.debouncedValue).toBe("external");
|
|
142
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("handles NaN external values without repeatedly resetting the draft", () => {
|
|
146
|
+
const onChange = vi.fn();
|
|
147
|
+
const { result, rerender } = renderHook(
|
|
148
|
+
({ value }) =>
|
|
149
|
+
useDebounceControlledState({ initialValue: value, onChange }),
|
|
150
|
+
{ initialProps: { value: Number.NaN } },
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
expect(result.current.value).toBeNaN();
|
|
154
|
+
act(() => result.current.onChange(1));
|
|
155
|
+
rerender({ value: Number.NaN });
|
|
156
|
+
expect(result.current.value).toBe(1);
|
|
157
|
+
rerender({ value: 2 });
|
|
158
|
+
expect(result.current.value).toBe(2);
|
|
159
|
+
rerender({ value: Number.NaN });
|
|
160
|
+
expect(result.current.value).toBeNaN();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("resets the draft when the external value changes from zero to negative zero", () => {
|
|
164
|
+
const onChange = vi.fn();
|
|
165
|
+
const { result, rerender } = renderHook(
|
|
166
|
+
({ value }) =>
|
|
167
|
+
useDebounceControlledState({ initialValue: value, onChange }),
|
|
168
|
+
{ initialProps: { value: 0 } },
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
act(() => result.current.onChange(1));
|
|
172
|
+
rerender({ value: -0 });
|
|
173
|
+
expect(result.current.value).toBe(-0);
|
|
174
|
+
});
|
|
124
175
|
});
|
|
125
176
|
|
|
126
177
|
describe("useDebouncedCallback", () => {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import { renderHook } from "@testing-library/react";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { useDeepCompareMemoize } from "../useDeepCompareMemoize";
|
|
5
|
+
|
|
6
|
+
describe("useDeepCompareMemoize", () => {
|
|
7
|
+
it("keeps equal values stable and updates changed values immediately", () => {
|
|
8
|
+
const initial = { nested: [1, 2] };
|
|
9
|
+
const { result, rerender } = renderHook(
|
|
10
|
+
({ value }) => useDeepCompareMemoize(value),
|
|
11
|
+
{
|
|
12
|
+
initialProps: { value: initial },
|
|
13
|
+
},
|
|
14
|
+
);
|
|
15
|
+
rerender({ value: { nested: [1, 2] } });
|
|
16
|
+
expect(result.current).toBe(initial);
|
|
17
|
+
const changed = { nested: [1, 3] };
|
|
18
|
+
rerender({ value: changed });
|
|
19
|
+
expect(result.current).toBe(changed);
|
|
20
|
+
rerender({ value: { nested: [1, 3] } });
|
|
21
|
+
expect(result.current).toBe(changed);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("handles inline callbacks without causing additional renders", () => {
|
|
25
|
+
let renders = 0;
|
|
26
|
+
const { result, rerender } = renderHook(
|
|
27
|
+
({ value }) => {
|
|
28
|
+
renders++;
|
|
29
|
+
return useDeepCompareMemoize({ callback: () => value });
|
|
30
|
+
},
|
|
31
|
+
{ initialProps: { value: "initial" } },
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const initialRenders = renders;
|
|
35
|
+
rerender({ value: "changed" });
|
|
36
|
+
expect(result.current.callback()).toBe("changed");
|
|
37
|
+
expect(renders).toBe(initialRenders + 1);
|
|
38
|
+
});
|
|
39
|
+
});
|