@marimo-team/islands 0.25.3-dev10 → 0.25.3-dev101
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ConnectedDataExplorerComponent-CNMliVzw.js → ConnectedDataExplorerComponent-Dhf5EGs5.js} +14 -14
- package/dist/{ErrorBoundary-BsqSMK9Y.js → ErrorBoundary-kKLKZNGz.js} +8 -8
- package/dist/{abnfDiagram-VCTEODGH-D-Ez91gV.js → abnfDiagram-VCTEODGH-Di400kYu.js} +2 -2
- package/dist/any-language-editor-k2B8YI3Y.js +75 -0
- package/dist/{architectureDiagram-5GKGNRK7-CTpcNtO8.js → architectureDiagram-5GKGNRK7-BdTJcsNJ.js} +1 -1
- package/dist/{arrays-CN1djC4k.js → arrays-R6JsImiT.js} +8 -0
- package/dist/assets/__vite-browser-external-35VXYmnw.js +1 -0
- package/dist/assets/{worker-HPY9fbhz.js → worker-Dq8y-WM5.js} +2 -2
- package/dist/{button-CvjkwdSk.js → button-BxB3RDjN.js} +1 -1
- package/dist/{capabilities-CQ5IjhzL.js → capabilities-BgR0osfN.js} +2 -2
- package/dist/{chat-ui-0lKdOoQ3.js → chat-ui-BIgPxfPP.js} +1141 -1141
- package/dist/{check-EDwSwP89.js → check-P35dc2dH.js} +1 -1
- package/dist/{chunk-IMKFNOWR-DGxi-zeo.js → chunk-IMKFNOWR-mtD7aJBw.js} +1 -1
- package/dist/{chunk-SHT3W25Y-CV9J3rAB.js → chunk-SHT3W25Y-DybB1ShH.js} +1 -1
- package/dist/{chunk-SVP7TREG-BL-9Fqgg.js → chunk-SVP7TREG-DNhhdCex.js} +1 -1
- package/dist/{chunk-TICWLB2K-Cu_XMjDM.js → chunk-TICWLB2K-NhAQhlDf.js} +1 -1
- package/dist/{chunk-YOKDWASO-DPZk8vhS.js → chunk-YOKDWASO-DKplTrBY.js} +20 -20
- package/dist/{classDiagram-ZZMXUADV-BXXV13Hq.js → classDiagram-ZZMXUADV-GTL0l5xS.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-B7FXJ1Cp.js → classDiagram-v2-VYDZK3BY-BWrM2ruI.js} +2 -2
- package/dist/{code-visibility-CXQvUXHT.js → code-visibility-CK84K0J7.js} +6 -6
- package/dist/{common-CuD-CAC-.js → common-DbG2Uufx.js} +5550 -5451
- package/dist/{copy-icon-Ft7t9bqz.js → copy-icon-CPoxUG7G.js} +3 -3
- package/dist/{cynefinDiagram-5FMLGOSQ-CH96rwba.js → cynefinDiagram-5FMLGOSQ-CX22wa7c.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-BD3PmpFY.js → dagre-GXQ25YYZ-Df6Pp-cl.js} +1 -1
- package/dist/{data-grid-overlay-editor-DgeyYmC1.js → data-grid-overlay-editor-BggIiNWF.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-CctU9kVx.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-muQtNZc4.js → diagram-UQ7AKVKN-FVHjCF9j.js} +1 -1
- package/dist/{diagram-VX7I27RA-CdhuZAqR.js → diagram-VX7I27RA-BSDbwFFv.js} +1 -1
- package/dist/{diagram-Z3DM3KII-BD8mXjOj.js → diagram-Z3DM3KII-BX8kW7Nc.js} +1 -1
- package/dist/{dialog-CEBFZNyc.js → dialog-C1PcpCqX.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-B_7xD2X4.js} +177 -169
- package/dist/{ebnfDiagram-PWID7BFC-BIN1sweE.js → ebnfDiagram-PWID7BFC-DtapdLet.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-Ctp5cfoN.js → erDiagram-RLTQ6QDP-BAru9jHt.js} +1 -1
- package/dist/{error-banner-D-WCIiMt.js → error-banner-Sl2FlSJ4.js} +2 -2
- package/dist/{error-entries-DSvFWT_M.js → error-entries-TAO6kEhF.js} +797 -784
- package/dist/{feedback-button-DZMj9PF3.js → feedback-button-CRF9v3d1.js} +29 -29
- package/dist/{flowDiagram-HODETNUW-3EfLrlFb.js → flowDiagram-HODETNUW-CjaeFeea.js} +2 -2
- package/dist/{formats-CeOmU1MU.js → formats-V8ifA0Bk.js} +3 -3
- package/dist/{ganttDiagram-EL5Y4UJY-BQT16JSc.js → ganttDiagram-EL5Y4UJY-Dh_4cBJZ.js} +2 -2
- package/dist/{glide-data-editor-g_L8FLAC.js → glide-data-editor-DgZRBzBJ.js} +370 -368
- package/dist/{glide-utils-DJyqh0EM.js → glide-utils-Bt8_pBa2.js} +16 -16
- package/dist/{globals-CYGlmBXb.js → globals-Bxo0esfB.js} +3 -3
- package/dist/{highlighted-body-KPVGNVTW-zwjq21tf.js → highlighted-body-KPVGNVTW-3Ziwfz-V.js} +2 -2
- package/dist/{html-to-image-B8s5SgGm.js → html-to-image-CWdayGZU.js} +29 -29
- package/dist/{infoDiagram-27XIBGKW-DipQoHxl.js → infoDiagram-27XIBGKW-B1hmUggC.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-DgFMjSGz.js → ishikawaDiagram-5VMMS53U-DUeT3t-d.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-w1ACVjgl.js → kanban-definition-UXKFOSKX-DEUnNNgc.js} +1 -1
- package/dist/{label-BDKF3Afc.js → label-BRnUC0NX.js} +2 -2
- package/dist/{linear-Cnaq3Tfb.js → linear-DWERh7eI.js} +121 -73
- package/dist/{loader-BQOfjTIJ.js → loader-Bm8cy3e8.js} +12 -12
- package/dist/main.js +3236 -3019
- package/dist/{mermaid-CHo49Yv-.js → mermaid-B0fXpsNP.js} +38 -38
- package/dist/mermaid-HWGCJPDP-Du3-HPQA.js +2 -0
- package/dist/{mindmap-definition-YA3MSWOX-CrT0KSfX.js → mindmap-definition-YA3MSWOX-CPexlhDQ.js} +1 -1
- package/dist/{number-overlay-editor-9VRv-pdj.js → number-overlay-editor-CI5EhMRq.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-CMpp1OOQ.js} +2 -2
- package/dist/{pieDiagram-E7YTZNPT-BfxJ_XCc.js → pieDiagram-E7YTZNPT-DlMWg5t1.js} +1 -1
- package/dist/precisionRound-DhAzeRcm.js +49 -0
- package/dist/{process-output-CKRXyszb.js → process-output-DD-oSvZk.js} +4 -4
- package/dist/{quadrantDiagram-AXDQQJYC-Ccw0xMjN.js → quadrantDiagram-AXDQQJYC-Blrm7V7_.js} +1 -1
- package/dist/{railroadDiagram-O6MQD6OU-8vFLVTPu.js → railroadDiagram-O6MQD6OU-DBJfseeO.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-BC7V639r.js} +1 -1
- package/dist/{reveal-component-C87mv9Mn.js → reveal-component-Y4-XPwJF.js} +23 -23
- package/dist/{select-BButFi-d.js → select-B0LRDH0g.js} +4 -4
- package/dist/{sequenceDiagram-WJ2MYXX4-CXFMnxGh.js → sequenceDiagram-WJ2MYXX4-CgesU6F-.js} +1 -1
- package/dist/{spec-BwarBpUJ.js → spec-CcT0slVc.js} +4 -4
- package/dist/{stateDiagram-D77RDMKH-DQeA_CwI.js → stateDiagram-D77RDMKH-lH_BUhs3.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-BxxQucm1.js → stateDiagram-v2-MP3YSRHH-8LL6FrBe.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/{swimlanes-42K2YHIH-Do6L-cYg.js → swimlanes-42K2YHIH-Cuf9B9kE.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-DUzPvSVU.js → swimlanesDiagram-VR7AAH4N-BoMMtOPh.js} +2 -2
- package/dist/{swiper-component-DKntyn8p.js → swiper-component-DfhRvAFi.js} +2 -2
- package/dist/{time-BieKwdYS.js → time-BaMbJs4z.js} +18 -18
- package/dist/{timeline-definition-24CTP7MA-D3c46WM5.js → timeline-definition-24CTP7MA-yzzw4TF4.js} +1 -1
- package/dist/{toDate-vIXljnsr.js → toDate-DKH0_kMo.js} +29 -31
- package/dist/{toast-B9UMEi8u.js → toast-CFWpihJU.js} +4 -4
- package/dist/{tooltip-BO1REbqs.js → tooltip-CKO1pHJo.js} +2 -2
- package/dist/{use-restore-focus-BEM7sFWl.js → use-restore-focus-DBCITLeF.js} +1 -1
- package/dist/{useAsyncData-Deh5kszT.js → useAsyncData-D0mLJstx.js} +15 -14
- package/dist/{useDateFormatter-D8XruNGG.js → useDateFormatter-c_yJtkAe.js} +2 -2
- package/dist/{useDeepCompareMemoize-DXwzRINI.js → useDeepCompareMemoize-BpPWi3G3.js} +1 -1
- package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities-BkdeVdXC.js} +1 -1
- package/dist/{useLifecycle-j7D9uD1o.js → useLifecycle-Dum8WSXq.js} +3 -3
- package/dist/{useTheme-CRnjkku0.js → useTheme-CfeaVw5D.js} +6 -6
- package/dist/{vega-component-DTG6zxaY.js → vega-component-DgytCKrL.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-eJOrTqqC.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-48cUWkfA.js → wardleyDiagram-VM6X3IG4-TILPvee6.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-CswYgYeu.js → xychartDiagram-S5SC5T6Z-DUPvRwwJ.js} +2 -2
- package/package.json +7 -7
- package/src/__tests__/host-imports.test.ts +1 -0
- package/src/components/app-config/ai-config.tsx +7 -2
- package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
- package/src/components/chat/acp/__tests__/state.test.ts +2 -2
- package/src/components/chat/acp/blocks.tsx +2 -2
- package/src/components/chat/chat-components.tsx +2 -0
- package/src/components/chat/chat-panel.tsx +9 -8
- package/src/components/chat/chat-utils.ts +4 -8
- package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
- package/src/components/data-table/__tests__/columns.test.tsx +105 -0
- package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
- package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
- package/src/components/data-table/__tests__/export-actions.test.tsx +105 -0
- package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
- package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
- package/src/components/data-table/cell-selection/feature.ts +2 -2
- package/src/components/data-table/cell-selection/types.ts +2 -2
- package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
- package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
- package/src/components/data-table/charts/charts.tsx +35 -25
- package/src/components/data-table/charts/storage.ts +16 -5
- package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
- package/src/components/data-table/column-summary/column-summary.tsx +2 -4
- package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
- package/src/components/data-table/columns.tsx +45 -46
- package/src/components/data-table/context-menu.tsx +36 -20
- package/src/components/data-table/data-table.tsx +7 -1
- package/src/components/data-table/date-popover.tsx +6 -4
- package/src/components/data-table/export-actions.tsx +76 -48
- package/src/components/data-table/row-viewer-panel/row-viewer.tsx +2 -2
- package/src/components/data-table/search-highlight.tsx +79 -0
- package/src/components/data-table/types.ts +1 -0
- package/src/components/data-table/url-detector.tsx +49 -9
- package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
- package/src/components/datasources/__tests__/column-preview.test.tsx +63 -2
- package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
- package/src/components/datasources/column-preview.tsx +8 -2
- package/src/components/datasources/datasources.tsx +47 -35
- package/src/components/debugger/debugger-code.tsx +5 -0
- package/src/components/dependency-graph/panels.tsx +2 -1
- package/src/components/editor/SortableCell.tsx +1 -3
- package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
- package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +24 -0
- package/src/components/editor/actions/pair-with-agent-modal.tsx +18 -15
- package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
- package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
- package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
- package/src/components/editor/cell/CreateCellButton.tsx +8 -10
- package/src/components/editor/cell/DeleteButton.tsx +1 -0
- package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
- package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
- package/src/components/editor/cell/code/cell-editor.tsx +1 -1
- package/src/components/editor/cell/code/language-toggle.tsx +8 -7
- package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
- package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
- package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
- package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
- package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +12 -18
- package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +58 -1
- package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
- package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
- package/src/components/editor/chrome/wrapper/storage.ts +1 -1
- package/src/components/editor/connections/components.tsx +1 -1
- package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
- package/src/components/editor/controls/command-palette.tsx +22 -10
- package/src/components/editor/dynamic-favicon.tsx +21 -40
- package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +12 -23
- package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
- package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
- package/src/components/editor/file-tree/file-explorer.tsx +10 -5
- package/src/components/editor/file-tree/file-viewer.tsx +4 -2
- package/src/components/editor/file-tree/use-file-search.ts +35 -21
- package/src/components/editor/notebook-banner.tsx +1 -0
- package/src/components/editor/output/MarimoTracebackOutput.tsx +2 -2
- package/src/components/editor/output/ansi-reduce.ts +6 -0
- package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
- package/src/components/editor/package-alert.tsx +6 -4
- package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
- package/src/components/home/components.tsx +17 -31
- package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
- package/src/components/icons/large-spinner.tsx +3 -14
- package/src/components/icons/spinner.tsx +1 -1
- package/src/components/mcp/mcp-status-indicator.tsx +1 -1
- package/src/components/pages/__tests__/home-page.test.tsx +116 -0
- package/src/components/pages/home-page.tsx +164 -91
- package/src/components/scratchpad/scratchpad.tsx +2 -2
- package/src/components/slides/reveal-component.tsx +4 -2
- package/src/components/slides/slide-cell-view.tsx +1 -1
- package/src/components/slides/slide-form.tsx +1 -0
- package/src/components/tracing/tracing.tsx +2 -2
- package/src/components/ui/alert-dialog.tsx +1 -1
- package/src/core/ai/context/providers/datasource.ts +6 -2
- package/src/core/attachments/state.ts +5 -8
- package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -0
- package/src/core/cells/__tests__/cell.test.ts +39 -0
- package/src/core/cells/__tests__/cells.test.ts +3 -1
- package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
- package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
- package/src/core/cells/cell.ts +14 -3
- package/src/core/cells/cells.ts +3 -1
- package/src/core/cells/pending-delete-service.ts +2 -6
- package/src/core/cells/session.ts +1 -1
- package/src/core/cells/types.ts +10 -1
- package/src/core/codemirror/__tests__/setup.test.ts +1 -1
- package/src/core/codemirror/keymaps/vim.ts +2 -6
- package/src/core/codemirror/language/languages/markdown.ts +1 -1
- package/src/core/codemirror/language/languages/python.ts +3 -3
- package/src/core/codemirror/language/panel/panel.tsx +1 -0
- package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
- package/src/core/datasets/data-source-discovery.ts +4 -6
- package/src/core/document/__tests__/draft.test.ts +322 -0
- package/src/core/document/__tests__/fixtures.ts +92 -0
- package/src/core/document/__tests__/lifecycle-scenarios.test.ts +61 -0
- package/src/core/document/__tests__/model.test.ts +158 -0
- package/src/core/document/__tests__/protocol-fixtures.ts +38 -0
- package/src/core/document/__tests__/recovery-scenarios.test.ts +230 -0
- package/src/core/document/__tests__/scenario-fixture.ts +42 -0
- package/src/core/document/__tests__/scenarios.test.ts +205 -0
- package/src/core/document/__tests__/state.test.ts +446 -0
- package/src/core/document/__tests__/submission.test.ts +201 -0
- package/src/core/document/__tests__/test-host.test.ts +92 -0
- package/src/core/document/__tests__/test-host.ts +247 -0
- package/src/core/document/__tests__/test-notebook.ts +139 -0
- package/src/core/document/__tests__/user.ts +61 -0
- package/src/core/document/draft.ts +472 -0
- package/src/core/document/index.ts +10 -0
- package/src/core/document/model.ts +53 -0
- package/src/core/document/state.ts +401 -0
- package/src/core/document/sync.ts +359 -0
- package/src/core/edit-app.tsx +2 -2
- package/src/core/host/AGENTS.md +12 -2
- package/src/core/host/__tests__/attachments.test.ts +56 -0
- package/src/core/host/__tests__/document.test.ts +126 -0
- package/src/core/host/__tests__/fixtures.ts +41 -0
- package/src/core/host/__tests__/notebook-client.test.ts +570 -0
- package/src/core/host/connection.ts +42 -0
- package/src/core/host/notebook-client.ts +297 -0
- package/src/core/host/resources/attachments.ts +22 -0
- package/src/core/host/resources/document.ts +101 -0
- package/src/core/hotkeys/actions.ts +2 -4
- package/src/core/islands/__tests__/bootstrap.test.ts +0 -26
- package/src/core/islands/bootstrap.ts +0 -1
- package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
- package/src/core/network/connection.ts +4 -2
- package/src/core/network/useConnectionNotice.ts +8 -2
- package/src/core/run-app.tsx +2 -2
- package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
- package/src/core/runtime/__tests__/runtime.test.ts +18 -17
- package/src/core/runtime/runtime.ts +26 -30
- package/src/core/state/__tests__/jotai.test.tsx +108 -0
- package/src/core/state/jotai.ts +1 -1
- package/src/core/static/files.ts +2 -0
- package/src/core/wasm/bridge.ts +2 -10
- package/src/core/wasm/store.ts +3 -3
- package/src/core/wasm/worker/bootstrap.ts +1 -1
- package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
- package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -29
- package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +223 -0
- package/src/core/websocket/__tests__/useNotebookSession.hook.test.tsx +606 -0
- package/src/core/websocket/connection-state.ts +60 -0
- package/src/core/websocket/state.ts +7 -0
- package/src/core/websocket/transports/__tests__/notebook.test.ts +502 -0
- package/src/core/websocket/transports/notebook.ts +213 -0
- package/src/core/websocket/types.ts +9 -39
- package/src/core/websocket/useNotebookClient.ts +89 -0
- package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +53 -233
- package/src/css/globals.css +5 -0
- package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
- package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
- package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
- package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
- package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
- package/src/hooks/__tests__/useScript.test.tsx +77 -0
- package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
- package/src/hooks/useAsyncData.ts +3 -3
- package/src/hooks/useDataSourceDiscovery.ts +6 -4
- package/src/hooks/useDebounce.ts +5 -8
- package/src/hooks/useDeepCompareMemoize.ts +1 -1
- package/src/hooks/useDelayElapsed.ts +14 -10
- package/src/hooks/useElapsedTime.ts +3 -3
- package/src/hooks/useInputHistory.ts +4 -7
- package/src/hooks/useInternalStateWithSync.ts +6 -6
- package/src/hooks/useResizeObserver.ts +4 -7
- package/src/hooks/useScript.ts +67 -70
- package/src/hooks/useStateForKey.ts +93 -0
- package/src/plugins/core/registerReactComponent.tsx +1 -1
- package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
- package/src/plugins/impl/DataTablePlugin.tsx +14 -15
- package/src/plugins/impl/DropdownPlugin.tsx +2 -6
- package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
- package/src/plugins/impl/FormPlugin.tsx +1 -1
- package/src/plugins/impl/RangeSliderPlugin.tsx +0 -1
- package/src/plugins/impl/RefreshPlugin.tsx +1 -0
- package/src/plugins/impl/SliderPlugin.tsx +6 -8
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +117 -1
- package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
- package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
- package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
- package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
- package/src/plugins/impl/anywidget/runtime.ts +21 -9
- package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
- package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
- package/src/plugins/impl/chat/chat-ui.tsx +12 -10
- package/src/plugins/impl/code/any-language-editor.tsx +2 -2
- package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
- package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
- package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
- package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
- package/src/plugins/impl/data-frames/DataFramePlugin.tsx +0 -1
- package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
- package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
- package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
- package/src/plugins/impl/panel/utils.ts +17 -0
- package/src/plugins/impl/vega/make-selectable.ts +4 -7
- package/src/plugins/layout/RoutesPlugin.tsx +1 -1
- package/src/stories/__tests__/editor-story.test.tsx +33 -0
- package/src/stories/button.stories.tsx +1 -0
- package/src/stories/editor.stories.tsx +24 -17
- package/src/stories/form-wrapper.stories.tsx +2 -2
- package/src/stories/select.stories.tsx +2 -2
- package/src/stories/switchable-multi-select.stories.tsx +1 -1
- package/src/utils/__tests__/arrays.test.ts +35 -0
- package/src/utils/__tests__/batch-requests.test.ts +3 -3
- package/src/utils/__tests__/numbers.test.ts +48 -1
- package/src/utils/__tests__/objects.test.ts +21 -0
- package/src/utils/arrays.ts +10 -0
- package/src/utils/blob.ts +1 -1
- package/src/utils/numbers.ts +28 -17
- package/src/utils/objects.ts +3 -7
- package/src/utils/once.ts +19 -19
- package/src/utils/zod-utils.ts +1 -3
- package/dist/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__/protocol.test.ts +0 -214
- package/src/core/host/protocol.ts +0 -1943
- package/src/core/state/__tests__/jotai.test.ts +0 -25
- package/src/core/websocket/__tests__/useMarimoKernelConnection.hook.test.tsx +0 -789
- package/src/core/websocket/__tests__/useWebSocket.test.ts +0 -49
- package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
- package/src/core/websocket/transports/__tests__/sse.test.ts +0 -338
- package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
- package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
- package/src/core/websocket/transports/basic.ts +0 -102
- package/src/core/websocket/transports/sse.ts +0 -308
- package/src/core/websocket/transports/transport.ts +0 -61
- package/src/core/websocket/transports/ws.ts +0 -98
- package/src/core/websocket/useWebSocket.tsx +0 -88
|
@@ -112,12 +112,16 @@ export const DatasetColumnPreview: React.FC<{
|
|
|
112
112
|
|
|
113
113
|
const addDataframeChart = preview.chart_code &&
|
|
114
114
|
table.source_type === "local" && (
|
|
115
|
-
<AddDataframeChart
|
|
115
|
+
<AddDataframeChart
|
|
116
|
+
chartCode={preview.chart_code}
|
|
117
|
+
columnName={column.name}
|
|
118
|
+
/>
|
|
116
119
|
);
|
|
117
120
|
|
|
118
121
|
const addSQLChart = table.source_type === "duckdb" && (
|
|
119
122
|
<Tooltip content="Add SQL cell" delayDuration={400}>
|
|
120
123
|
<Button
|
|
124
|
+
aria-label={`Add SQL cell for ${column.name}`}
|
|
121
125
|
variant="outline"
|
|
122
126
|
size="icon"
|
|
123
127
|
className="z-10 bg-background absolute right-1 -top-1"
|
|
@@ -238,7 +242,8 @@ export function renderChart(chartSpec: string, theme: Theme) {
|
|
|
238
242
|
|
|
239
243
|
export const AddDataframeChart: React.FC<{
|
|
240
244
|
chartCode: string;
|
|
241
|
-
|
|
245
|
+
columnName: string;
|
|
246
|
+
}> = ({ chartCode, columnName }) => {
|
|
242
247
|
const autoInstantiate = useAtomValue(autoInstantiateAtom);
|
|
243
248
|
const lastFocusedCellId = useLastFocusedCellId();
|
|
244
249
|
const { createNewCell } = useCellActions();
|
|
@@ -261,6 +266,7 @@ export const AddDataframeChart: React.FC<{
|
|
|
261
266
|
return (
|
|
262
267
|
<Tooltip content="Add chart to notebook" delayDuration={400}>
|
|
263
268
|
<Button
|
|
269
|
+
aria-label={`Add chart to notebook for ${columnName}`}
|
|
264
270
|
variant="outline"
|
|
265
271
|
size="icon"
|
|
266
272
|
className="z-10 bg-background absolute right-1 -top-0.5"
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
VisibilityToggleButton,
|
|
14
14
|
} from "@/components/editor/file-tree/tree-actions";
|
|
15
15
|
import { CopyClipboardIcon } from "@/components/icons/copy-icon";
|
|
16
|
-
import { Button } from "@/components/ui/button";
|
|
16
|
+
import { Button, buttonVariants } from "@/components/ui/button";
|
|
17
17
|
import { Command, CommandInput, CommandItem } from "@/components/ui/command";
|
|
18
18
|
import { Tooltip } from "@/components/ui/tooltip";
|
|
19
19
|
import { maybeAddMarimoImport } from "@/core/cells/add-missing-import";
|
|
@@ -702,7 +702,40 @@ const SchemaNode: React.FC<SchemaNodeProps> = (props) => {
|
|
|
702
702
|
);
|
|
703
703
|
};
|
|
704
704
|
|
|
705
|
-
|
|
705
|
+
// Kept outside the component because the React Compiler does not yet
|
|
706
|
+
// support try/finally inside component bodies.
|
|
707
|
+
async function loadTableList(
|
|
708
|
+
sqlTableContext: SQLTableContext,
|
|
709
|
+
addTableList: ReturnType<typeof useDataSourceActions>["addTableList"],
|
|
710
|
+
setLoading: (loading: boolean) => void,
|
|
711
|
+
) {
|
|
712
|
+
setLoading(true);
|
|
713
|
+
try {
|
|
714
|
+
const { engine, database, schema, schemaPath } = sqlTableContext;
|
|
715
|
+
const previewTableList = await PreviewSQLTableList.request({
|
|
716
|
+
engine: engine,
|
|
717
|
+
database: database,
|
|
718
|
+
schema: schema,
|
|
719
|
+
schemaPath: schemaPath ?? [],
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
if (!previewTableList?.tables) {
|
|
723
|
+
throw new Error("No tables available");
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
addTableList({
|
|
727
|
+
tables: previewTableList.tables,
|
|
728
|
+
sqlTableContext: sqlTableContext,
|
|
729
|
+
});
|
|
730
|
+
} finally {
|
|
731
|
+
setLoading(false);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* This is exported for testing purposes only.
|
|
737
|
+
*/
|
|
738
|
+
export const TableList: React.FC<{
|
|
706
739
|
tables: DataTable[];
|
|
707
740
|
sqlTableContext?: SQLTableContext;
|
|
708
741
|
searchValue?: string;
|
|
@@ -731,26 +764,7 @@ const TableList: React.FC<{
|
|
|
731
764
|
// resets tablesResolved to false).
|
|
732
765
|
const { isPending, error } = useAsyncData(async () => {
|
|
733
766
|
if (!tablesResolved && sqlTableContext) {
|
|
734
|
-
setTablesLoading
|
|
735
|
-
|
|
736
|
-
const { engine, database, schema, schemaPath } = sqlTableContext;
|
|
737
|
-
const previewTableList = await PreviewSQLTableList.request({
|
|
738
|
-
engine: engine,
|
|
739
|
-
database: database,
|
|
740
|
-
schema: schema,
|
|
741
|
-
schemaPath: schemaPath ?? [],
|
|
742
|
-
});
|
|
743
|
-
|
|
744
|
-
if (!previewTableList?.tables) {
|
|
745
|
-
setTablesLoading(false);
|
|
746
|
-
throw new Error("No tables available");
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
addTableList({
|
|
750
|
-
tables: previewTableList.tables,
|
|
751
|
-
sqlTableContext: sqlTableContext,
|
|
752
|
-
});
|
|
753
|
-
setTablesLoading(false);
|
|
767
|
+
await loadTableList(sqlTableContext, addTableList, setTablesLoading);
|
|
754
768
|
}
|
|
755
769
|
}, [tablesResolved, sqlTableContext]);
|
|
756
770
|
|
|
@@ -964,6 +978,7 @@ const DatasetTableItem: React.FC<{
|
|
|
964
978
|
{renderRowsByColumns()}
|
|
965
979
|
<Tooltip content="Add table to notebook" delayDuration={400}>
|
|
966
980
|
<Button
|
|
981
|
+
aria-label={`Add table ${table.name} to notebook`}
|
|
967
982
|
className="group-hover:inline-flex hidden"
|
|
968
983
|
variant="text"
|
|
969
984
|
size="icon"
|
|
@@ -1054,19 +1069,16 @@ const DatasetColumnItem: React.FC<{
|
|
|
1054
1069
|
{isIndexed &&
|
|
1055
1070
|
renderItemSubtext({ tooltipContent: "Indexed", content: "IDX" })}
|
|
1056
1071
|
</div>
|
|
1057
|
-
<
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
/>
|
|
1068
|
-
</Button>
|
|
1069
|
-
</Tooltip>
|
|
1072
|
+
<CopyClipboardIcon
|
|
1073
|
+
tooltip="Copy column name"
|
|
1074
|
+
value={column.name}
|
|
1075
|
+
ariaLabel={`Copy column name ${column.name}`}
|
|
1076
|
+
className="h-3 w-3"
|
|
1077
|
+
buttonClassName={cn(
|
|
1078
|
+
buttonVariants({ variant: "text", size: "icon" }),
|
|
1079
|
+
"group-hover:opacity-100 opacity-0 hover:bg-muted text-muted-foreground hover:text-foreground",
|
|
1080
|
+
)}
|
|
1081
|
+
/>
|
|
1070
1082
|
<span className="text-xs text-muted-foreground">
|
|
1071
1083
|
{column.external_type}
|
|
1072
1084
|
</span>
|
|
@@ -162,6 +162,7 @@ export const DebuggerControls: React.FC<{
|
|
|
162
162
|
<div className="flex">
|
|
163
163
|
<Tooltip content="Next line">
|
|
164
164
|
<Button
|
|
165
|
+
aria-label="Next line"
|
|
165
166
|
variant="text"
|
|
166
167
|
size="icon"
|
|
167
168
|
data-testid="debugger-next-button"
|
|
@@ -173,6 +174,7 @@ export const DebuggerControls: React.FC<{
|
|
|
173
174
|
</Tooltip>
|
|
174
175
|
<Tooltip content="Continue execution">
|
|
175
176
|
<Button
|
|
177
|
+
aria-label="Continue execution"
|
|
176
178
|
variant="text"
|
|
177
179
|
size="icon"
|
|
178
180
|
data-testid="debugger-continue-button"
|
|
@@ -187,6 +189,7 @@ export const DebuggerControls: React.FC<{
|
|
|
187
189
|
</Tooltip>
|
|
188
190
|
<Tooltip content="Print stack trace">
|
|
189
191
|
<Button
|
|
192
|
+
aria-label="Print stack trace"
|
|
190
193
|
variant="text"
|
|
191
194
|
size="icon"
|
|
192
195
|
data-testid="debugger-stack-button"
|
|
@@ -198,6 +201,7 @@ export const DebuggerControls: React.FC<{
|
|
|
198
201
|
</Tooltip>
|
|
199
202
|
<Tooltip content="Help">
|
|
200
203
|
<Button
|
|
204
|
+
aria-label="Debugger help"
|
|
201
205
|
variant="text"
|
|
202
206
|
size="icon"
|
|
203
207
|
data-testid="debugger-help-button"
|
|
@@ -210,6 +214,7 @@ export const DebuggerControls: React.FC<{
|
|
|
210
214
|
{onClear && (
|
|
211
215
|
<Tooltip content="Quit and clear">
|
|
212
216
|
<Button
|
|
217
|
+
aria-label="Quit and clear debugger"
|
|
213
218
|
variant="text"
|
|
214
219
|
size="icon"
|
|
215
220
|
data-testid="debugger-clear-button"
|
|
@@ -216,11 +216,12 @@ export const GraphSelectionPanel: React.FC<{
|
|
|
216
216
|
</Button>
|
|
217
217
|
</Tooltip>
|
|
218
218
|
<ConnectionCellActionsDropdown cellId={selection.id}>
|
|
219
|
-
<Button variant="ghost" size="icon">
|
|
219
|
+
<Button aria-label="Cell actions" variant="ghost" size="icon">
|
|
220
220
|
<MoreVerticalIcon className="w-4 h-4" />
|
|
221
221
|
</Button>
|
|
222
222
|
</ConnectionCellActionsDropdown>
|
|
223
223
|
<Button
|
|
224
|
+
aria-label="Close cell details"
|
|
224
225
|
variant="text"
|
|
225
226
|
size="icon"
|
|
226
227
|
onClick={() => {
|
|
@@ -71,7 +71,6 @@ export const SortableCell = React.forwardRef(
|
|
|
71
71
|
return (
|
|
72
72
|
<SortableCellInternal
|
|
73
73
|
ref={ref}
|
|
74
|
-
cellId={cellId}
|
|
75
74
|
canMoveX={canMoveX}
|
|
76
75
|
{...props}
|
|
77
76
|
attributes={attributes}
|
|
@@ -86,7 +85,7 @@ export const SortableCell = React.forwardRef(
|
|
|
86
85
|
);
|
|
87
86
|
SortableCell.displayName = "SortableCell";
|
|
88
87
|
|
|
89
|
-
interface SortableCellInternalProps extends Props {
|
|
88
|
+
interface SortableCellInternalProps extends Omit<Props, "cellId"> {
|
|
90
89
|
children: React.ReactNode;
|
|
91
90
|
attributes: DraggableAttributes;
|
|
92
91
|
listeners: SyntheticListenerMap | undefined;
|
|
@@ -99,7 +98,6 @@ interface SortableCellInternalProps extends Props {
|
|
|
99
98
|
const SortableCellInternal = React.forwardRef(
|
|
100
99
|
(
|
|
101
100
|
{
|
|
102
|
-
cellId,
|
|
103
101
|
canMoveX,
|
|
104
102
|
children,
|
|
105
103
|
attributes,
|
|
@@ -1,17 +1,41 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
act,
|
|
5
|
+
render as renderComponent,
|
|
6
|
+
waitFor,
|
|
7
|
+
} from "@testing-library/react";
|
|
4
8
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
-
import {
|
|
9
|
+
import { Provider, createStore } from "jotai";
|
|
10
|
+
import type { ReactNode } from "react";
|
|
11
|
+
import { MockNotebook } from "@/__mocks__/notebook";
|
|
12
|
+
import { cellId } from "@/__tests__/branded";
|
|
13
|
+
import { notebookAtom } from "@/core/cells/cells";
|
|
6
14
|
import { DynamicFavicon } from "../dynamic-favicon";
|
|
7
15
|
|
|
8
|
-
// Mock useCellErrors hook
|
|
9
|
-
vi.mock("@/core/cells/cells", () => ({
|
|
10
|
-
useCellErrors: vi.fn(),
|
|
11
|
-
}));
|
|
12
|
-
|
|
13
16
|
describe("DynamicFavicon", () => {
|
|
14
17
|
let favicon: HTMLLinkElement;
|
|
18
|
+
let store: ReturnType<typeof createStore>;
|
|
19
|
+
const id = cellId("cell");
|
|
20
|
+
|
|
21
|
+
function render(ui: ReactNode) {
|
|
22
|
+
return renderComponent(ui, {
|
|
23
|
+
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function setErrors() {
|
|
28
|
+
store.set(
|
|
29
|
+
notebookAtom,
|
|
30
|
+
MockNotebook.notebookStateWithErrors([
|
|
31
|
+
{
|
|
32
|
+
cellId: id,
|
|
33
|
+
cellName: "cell",
|
|
34
|
+
errorData: [MockNotebook.errors.syntax("Invalid syntax")],
|
|
35
|
+
},
|
|
36
|
+
]),
|
|
37
|
+
);
|
|
38
|
+
}
|
|
15
39
|
|
|
16
40
|
beforeEach(() => {
|
|
17
41
|
document
|
|
@@ -27,15 +51,20 @@ describe("DynamicFavicon", () => {
|
|
|
27
51
|
// Mock document.hasFocus
|
|
28
52
|
vi.spyOn(document, "hasFocus").mockReturnValue(true);
|
|
29
53
|
|
|
30
|
-
|
|
31
|
-
|
|
54
|
+
store = createStore();
|
|
55
|
+
store.set(
|
|
56
|
+
notebookAtom,
|
|
57
|
+
MockNotebook.notebookState({
|
|
58
|
+
cellData: { [id]: { code: "x = 1" } },
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
32
61
|
});
|
|
33
62
|
|
|
34
63
|
afterEach(() => {
|
|
35
64
|
document
|
|
36
65
|
.querySelectorAll("link[rel~='icon']")
|
|
37
66
|
.forEach((element) => element.remove());
|
|
38
|
-
vi.
|
|
67
|
+
vi.restoreAllMocks();
|
|
39
68
|
vi.useRealTimers();
|
|
40
69
|
});
|
|
41
70
|
|
|
@@ -67,9 +96,7 @@ describe("DynamicFavicon", () => {
|
|
|
67
96
|
});
|
|
68
97
|
|
|
69
98
|
it("should show error favicon when run completes with errors", async () => {
|
|
70
|
-
(
|
|
71
|
-
{ error: "mock error" },
|
|
72
|
-
]);
|
|
99
|
+
setErrors();
|
|
73
100
|
|
|
74
101
|
const { rerender } = render(<DynamicFavicon isRunning={true} />);
|
|
75
102
|
|
|
@@ -147,6 +174,72 @@ describe("DynamicFavicon", () => {
|
|
|
147
174
|
});
|
|
148
175
|
});
|
|
149
176
|
|
|
177
|
+
it("does not look up or rewrite the favicon when code changes", async () => {
|
|
178
|
+
const { rerender } = render(<DynamicFavicon isRunning={true} />);
|
|
179
|
+
await waitFor(() => expect(favicon.href).toContain("circle-play.ico"));
|
|
180
|
+
rerender(<DynamicFavicon isRunning={false} />);
|
|
181
|
+
await waitFor(() => expect(favicon.href).toContain("circle-check.ico"));
|
|
182
|
+
const lookup = vi.spyOn(document, "querySelector");
|
|
183
|
+
const write = vi.spyOn(favicon, "href", "set");
|
|
184
|
+
|
|
185
|
+
await act(async () => {
|
|
186
|
+
store.set(notebookAtom, (state) => ({
|
|
187
|
+
...state,
|
|
188
|
+
cellData: {
|
|
189
|
+
...state.cellData,
|
|
190
|
+
[id]: { ...state.cellData[id], code: "x = 2" },
|
|
191
|
+
},
|
|
192
|
+
}));
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
expect(
|
|
196
|
+
lookup.mock.calls.filter(
|
|
197
|
+
([selector]) => selector === "link[rel~='icon']",
|
|
198
|
+
),
|
|
199
|
+
).toEqual([]);
|
|
200
|
+
expect(write).not.toHaveBeenCalled();
|
|
201
|
+
lookup.mockRestore();
|
|
202
|
+
write.mockRestore();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("updates the favicon when the error count changes", async () => {
|
|
206
|
+
const { rerender } = render(<DynamicFavicon isRunning={true} />);
|
|
207
|
+
await waitFor(() => expect(favicon.href).toContain("circle-play.ico"));
|
|
208
|
+
rerender(<DynamicFavicon isRunning={false} />);
|
|
209
|
+
await waitFor(() => expect(favicon.href).toContain("circle-check.ico"));
|
|
210
|
+
|
|
211
|
+
act(() => setErrors());
|
|
212
|
+
await waitFor(() => expect(favicon.href).toContain("circle-x.ico"));
|
|
213
|
+
|
|
214
|
+
act(() =>
|
|
215
|
+
store.set(
|
|
216
|
+
notebookAtom,
|
|
217
|
+
MockNotebook.notebookState({ cellData: { [id]: {} } }),
|
|
218
|
+
),
|
|
219
|
+
);
|
|
220
|
+
await waitFor(() => expect(favicon.href).toContain("circle-check.ico"));
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("keeps the completion icon until its own reset timer expires", async () => {
|
|
224
|
+
vi.useFakeTimers();
|
|
225
|
+
const { rerender } = render(<DynamicFavicon isRunning={true} />);
|
|
226
|
+
await act(() => vi.advanceTimersByTimeAsync(1000));
|
|
227
|
+
rerender(<DynamicFavicon isRunning={false} />);
|
|
228
|
+
await act(() => vi.advanceTimersByTimeAsync(2000));
|
|
229
|
+
expect(favicon.href).toContain("circle-check.ico");
|
|
230
|
+
await act(() => vi.advanceTimersByTimeAsync(1000));
|
|
231
|
+
expect(favicon.href).toContain("favicon.ico");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("cancels the reset timer when unmounted", async () => {
|
|
235
|
+
vi.useFakeTimers();
|
|
236
|
+
const { unmount } = render(<DynamicFavicon isRunning={true} />);
|
|
237
|
+
await act(() => vi.advanceTimersByTimeAsync(0));
|
|
238
|
+
expect(vi.getTimerCount()).toBe(1);
|
|
239
|
+
unmount();
|
|
240
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
241
|
+
});
|
|
242
|
+
|
|
150
243
|
describe("notifications", () => {
|
|
151
244
|
beforeEach(() => {
|
|
152
245
|
vi.spyOn(document, "visibilityState", "get").mockReturnValue("hidden");
|
|
@@ -156,7 +249,7 @@ describe("DynamicFavicon", () => {
|
|
|
156
249
|
global.Notification.permission = "granted";
|
|
157
250
|
});
|
|
158
251
|
|
|
159
|
-
it("should send success notification when run completes without errors", () => {
|
|
252
|
+
it("should send success notification when run completes without errors", async () => {
|
|
160
253
|
// @ts-expect-error ok in tests
|
|
161
254
|
global.Notification = vi
|
|
162
255
|
.fn()
|
|
@@ -172,12 +265,11 @@ describe("DynamicFavicon", () => {
|
|
|
172
265
|
|
|
173
266
|
const { rerender } = render(<DynamicFavicon isRunning={true} />);
|
|
174
267
|
rerender(<DynamicFavicon isRunning={false} />);
|
|
268
|
+
await waitFor(() => expect(Notification).toHaveBeenCalledOnce());
|
|
175
269
|
});
|
|
176
270
|
|
|
177
|
-
it("should send error notification when run completes with errors", () => {
|
|
178
|
-
(
|
|
179
|
-
{ error: "mock error" },
|
|
180
|
-
]);
|
|
271
|
+
it("should send error notification when run completes with errors", async () => {
|
|
272
|
+
setErrors();
|
|
181
273
|
|
|
182
274
|
// @ts-expect-error ok in tests
|
|
183
275
|
global.Notification = vi
|
|
@@ -194,6 +286,7 @@ describe("DynamicFavicon", () => {
|
|
|
194
286
|
|
|
195
287
|
const { rerender } = render(<DynamicFavicon isRunning={true} />);
|
|
196
288
|
rerender(<DynamicFavicon isRunning={false} />);
|
|
289
|
+
await waitFor(() => expect(Notification).toHaveBeenCalledOnce());
|
|
197
290
|
});
|
|
198
291
|
|
|
199
292
|
it("should not send notification when document is visible", () => {
|
|
@@ -314,6 +314,30 @@ describe("PairWithAgentModal", () => {
|
|
|
314
314
|
expect(screen.getByRole("tabpanel")).not.toHaveTextContent("--notebook");
|
|
315
315
|
});
|
|
316
316
|
|
|
317
|
+
it("reverifies the target when host mode is enabled again for the same Session", async () => {
|
|
318
|
+
await act(async () => {
|
|
319
|
+
render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
|
|
320
|
+
});
|
|
321
|
+
expect(screen.getByRole("tabpanel")).toHaveTextContent(NOTEBOOK_ID);
|
|
322
|
+
act(() =>
|
|
323
|
+
store.set(pairConfigAtom, { ...PAIR_CONFIG, hostEnabled: false }),
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
const pending = new Deferred<Response>();
|
|
327
|
+
vi.mocked(fetch).mockImplementation((url) =>
|
|
328
|
+
String(url).endsWith("/api/sessions")
|
|
329
|
+
? pending.promise
|
|
330
|
+
: responseFor(url),
|
|
331
|
+
);
|
|
332
|
+
act(() => store.set(pairConfigAtom, PAIR_CONFIG));
|
|
333
|
+
expect(screen.getByRole("button", { name: "Copy command" })).toBeDisabled();
|
|
334
|
+
expect(screen.getByRole("tabpanel")).not.toHaveTextContent(NOTEBOOK_ID);
|
|
335
|
+
|
|
336
|
+
await act(async () => pending.resolve(await responseFor("/api/sessions")));
|
|
337
|
+
expect(screen.getByRole("button", { name: "Copy command" })).toBeEnabled();
|
|
338
|
+
expect(screen.getByRole("tabpanel")).toHaveTextContent(NOTEBOOK_ID);
|
|
339
|
+
});
|
|
340
|
+
|
|
317
341
|
it("keeps copying disabled when Notebook verification fails", async () => {
|
|
318
342
|
vi.mocked(fetch).mockImplementation((url) =>
|
|
319
343
|
String(url).endsWith("/api/sessions")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { CheckIcon, CopyIcon } from "lucide-react";
|
|
4
|
-
import React, { useEffect, useState } from "react";
|
|
4
|
+
import React, { useEffect, useMemo, useState } from "react";
|
|
5
5
|
import { useAtomValue } from "jotai";
|
|
6
6
|
import { Button } from "@/components/ui/button";
|
|
7
7
|
import {
|
|
@@ -53,21 +53,20 @@ export const PairWithAgentModal: React.FC<{
|
|
|
53
53
|
const config = useAtomValue(pairConfigAtom);
|
|
54
54
|
const hostEnabled = config?.hostEnabled ?? false;
|
|
55
55
|
const stableSessionId = useAtomValue(stableSessionIdAtom);
|
|
56
|
+
const lookupIdentity = useMemo(
|
|
57
|
+
() => ({ hostEnabled, stableSessionId }),
|
|
58
|
+
[hostEnabled, stableSessionId],
|
|
59
|
+
);
|
|
56
60
|
const [lookup, setLookup] = useState<
|
|
57
61
|
{
|
|
58
|
-
|
|
59
|
-
} & (
|
|
60
|
-
| { status: "ready"; notebookId: string | null }
|
|
61
|
-
| { status: "loading" | "error" }
|
|
62
|
-
)
|
|
62
|
+
identity: typeof lookupIdentity;
|
|
63
|
+
} & ({ status: "ready"; notebookId: string | null } | { status: "error" })
|
|
63
64
|
>();
|
|
64
65
|
useEffect(() => {
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
const { hostEnabled, stableSessionId: sessionId } = lookupIdentity;
|
|
67
|
+
if (!hostEnabled || !sessionId) {
|
|
67
68
|
return;
|
|
68
69
|
}
|
|
69
|
-
const sessionId = stableSessionId;
|
|
70
|
-
setLookup({ sessionId, status: "loading" });
|
|
71
70
|
let cancelled = false;
|
|
72
71
|
API.get<Record<string, { session_id: string; notebook_id: string | null }>>(
|
|
73
72
|
"/sessions",
|
|
@@ -79,22 +78,26 @@ export const PairWithAgentModal: React.FC<{
|
|
|
79
78
|
);
|
|
80
79
|
setLookup(
|
|
81
80
|
session
|
|
82
|
-
? {
|
|
83
|
-
|
|
81
|
+
? {
|
|
82
|
+
identity: lookupIdentity,
|
|
83
|
+
status: "ready",
|
|
84
|
+
notebookId: session.notebook_id,
|
|
85
|
+
}
|
|
86
|
+
: { identity: lookupIdentity, status: "error" },
|
|
84
87
|
);
|
|
85
88
|
}
|
|
86
89
|
})
|
|
87
90
|
.catch(() => {
|
|
88
91
|
if (!cancelled) {
|
|
89
|
-
setLookup({
|
|
92
|
+
setLookup({ identity: lookupIdentity, status: "error" });
|
|
90
93
|
}
|
|
91
94
|
});
|
|
92
95
|
return () => {
|
|
93
96
|
cancelled = true;
|
|
94
97
|
};
|
|
95
|
-
}, [
|
|
98
|
+
}, [lookupIdentity]);
|
|
96
99
|
const currentLookup =
|
|
97
|
-
lookup?.
|
|
100
|
+
lookup?.identity === lookupIdentity ? lookup : undefined;
|
|
98
101
|
const verified = !hostEnabled || currentLookup?.status === "ready";
|
|
99
102
|
const notebookId =
|
|
100
103
|
hostEnabled && currentLookup?.status === "ready"
|
|
@@ -106,9 +106,9 @@ export const AddCellWithAI: React.FC<{
|
|
|
106
106
|
const runtimeManager = useRuntimeManager();
|
|
107
107
|
|
|
108
108
|
const inputRef = useRef<ReactCodeMirrorRef>(null);
|
|
109
|
-
const submissionController =
|
|
110
|
-
new StagedCellSubmissionController(),
|
|
111
|
-
)
|
|
109
|
+
const [submissionController] = useState(
|
|
110
|
+
() => new StagedCellSubmissionController(),
|
|
111
|
+
);
|
|
112
112
|
|
|
113
113
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
114
114
|
const { files, addFiles, removeFile } = useFileState();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
import { DatabaseIcon, DiamondPlusIcon, PlusIcon } from "lucide-react";
|
|
3
|
-
import { useState } from "react";
|
|
3
|
+
import { useRef, useState } from "react";
|
|
4
4
|
import { Button } from "@/components/editor/inputs/Inputs";
|
|
5
5
|
import { MinimalHotkeys } from "@/components/shortcuts/renderShortcut";
|
|
6
6
|
import {
|
|
@@ -36,7 +36,8 @@ export const CreateCellButton = ({
|
|
|
36
36
|
const { createNewCell, addSetupCellIfDoesntExist } = useCellActions();
|
|
37
37
|
const shortcut = `${oneClickShortcut}-Click`;
|
|
38
38
|
const [open, setOpen] = useState(false);
|
|
39
|
-
|
|
39
|
+
// Time the dropdown last opened, to ignore the click that opened it
|
|
40
|
+
const openedAt = useRef(0);
|
|
40
41
|
|
|
41
42
|
const baseTooltipContent =
|
|
42
43
|
getConnectionTooltip(connectionState) || tooltipContent;
|
|
@@ -83,9 +84,7 @@ export const CreateCellButton = ({
|
|
|
83
84
|
|
|
84
85
|
const openDropdown = () => {
|
|
85
86
|
setOpen(true);
|
|
86
|
-
|
|
87
|
-
// Allow interactions after a brief delay to prevent the dropdown items immediately being clicked
|
|
88
|
-
setTimeout(() => setJustOpened(false), 200);
|
|
87
|
+
openedAt.current = Date.now();
|
|
89
88
|
};
|
|
90
89
|
|
|
91
90
|
// We use onPointerDownCapture (not onPointerDown) to intercept events in
|
|
@@ -121,15 +120,14 @@ export const CreateCellButton = ({
|
|
|
121
120
|
const handleOpenChange = (isOpen: boolean) => {
|
|
122
121
|
setOpen(isOpen);
|
|
123
122
|
if (isOpen) {
|
|
124
|
-
|
|
125
|
-
// Allow interactions after a brief delay
|
|
126
|
-
setTimeout(() => setJustOpened(false), 200);
|
|
123
|
+
openedAt.current = Date.now();
|
|
127
124
|
}
|
|
128
125
|
};
|
|
129
126
|
|
|
130
127
|
const handleFirstItemClick = (e: React.MouseEvent) => {
|
|
131
|
-
// Hack to prevent the first item from being clicked when the dropdown is
|
|
132
|
-
|
|
128
|
+
// Hack to prevent the first item from being clicked when the dropdown is
|
|
129
|
+
// opened: allow interactions only after a brief delay
|
|
130
|
+
if (Date.now() - openedAt.current < 200) {
|
|
133
131
|
e.preventDefault();
|
|
134
132
|
e.stopPropagation();
|
|
135
133
|
return;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { act, renderHook } from "@testing-library/react";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { useShouldShowInterrupt } from "../useShouldShowInterrupt";
|
|
6
|
+
|
|
7
|
+
describe("useShouldShowInterrupt", () => {
|
|
8
|
+
beforeEach(() => vi.useFakeTimers());
|
|
9
|
+
afterEach(() => vi.useRealTimers());
|
|
10
|
+
|
|
11
|
+
it("waits for each run and hides immediately when it stops", () => {
|
|
12
|
+
const { result, rerender } = renderHook(useShouldShowInterrupt, {
|
|
13
|
+
initialProps: false,
|
|
14
|
+
});
|
|
15
|
+
expect(result.current).toBe(false);
|
|
16
|
+
rerender(true);
|
|
17
|
+
expect(result.current).toBe(false);
|
|
18
|
+
act(() => vi.advanceTimersByTime(200));
|
|
19
|
+
expect(result.current).toBe(true);
|
|
20
|
+
rerender(false);
|
|
21
|
+
expect(result.current).toBe(false);
|
|
22
|
+
rerender(true);
|
|
23
|
+
expect(result.current).toBe(false);
|
|
24
|
+
act(() => vi.advanceTimersByTime(199));
|
|
25
|
+
expect(result.current).toBe(false);
|
|
26
|
+
act(() => vi.advanceTimersByTime(1));
|
|
27
|
+
expect(result.current).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("cancels the delay for a short run", () => {
|
|
31
|
+
const { result, rerender } = renderHook(useShouldShowInterrupt, {
|
|
32
|
+
initialProps: true,
|
|
33
|
+
});
|
|
34
|
+
act(() => vi.advanceTimersByTime(100));
|
|
35
|
+
rerender(false);
|
|
36
|
+
act(() => vi.advanceTimersByTime(200));
|
|
37
|
+
expect(result.current).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
});
|