@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
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { usePrevious } from "@dnd-kit/utilities";
|
|
4
|
+
import { useAtomValue } from "jotai";
|
|
4
5
|
import { useEffect } from "react";
|
|
5
|
-
import {
|
|
6
|
+
import { cellErrorCount } from "@/core/cells/cells";
|
|
6
7
|
import { useEventListener } from "@/hooks/useEventListener";
|
|
7
8
|
import successFaviconUrl from "../../assets/circle-check.ico";
|
|
8
9
|
import runningFaviconUrl from "../../assets/circle-play.ico";
|
|
@@ -15,13 +16,6 @@ const FAVICON_PATHS = {
|
|
|
15
16
|
error: errorFaviconUrl,
|
|
16
17
|
} as const;
|
|
17
18
|
|
|
18
|
-
// Cache favicon object URLs lazily
|
|
19
|
-
type FaviconKey = keyof typeof FAVICON_PATHS;
|
|
20
|
-
|
|
21
|
-
async function getFaviconUrl(key: FaviconKey): Promise<string> {
|
|
22
|
-
return FAVICON_PATHS[key];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
19
|
function getOrCreateFavicon(): HTMLLinkElement {
|
|
26
20
|
const existingFavicon =
|
|
27
21
|
document.querySelector<HTMLLinkElement>("link[rel~='icon']");
|
|
@@ -44,16 +38,16 @@ function maybeSendNotification(numErrors: number) {
|
|
|
44
38
|
return;
|
|
45
39
|
}
|
|
46
40
|
|
|
47
|
-
const sendNotification =
|
|
41
|
+
const sendNotification = () => {
|
|
48
42
|
if (numErrors === 0) {
|
|
49
43
|
new Notification("Execution completed", {
|
|
50
44
|
body: "Your notebook run completed successfully.",
|
|
51
|
-
icon:
|
|
45
|
+
icon: FAVICON_PATHS.success,
|
|
52
46
|
});
|
|
53
47
|
} else {
|
|
54
48
|
new Notification("Execution failed", {
|
|
55
49
|
body: `Your notebook run encountered ${numErrors} error(s).`,
|
|
56
|
-
icon:
|
|
50
|
+
icon: FAVICON_PATHS.error,
|
|
57
51
|
});
|
|
58
52
|
}
|
|
59
53
|
};
|
|
@@ -75,7 +69,7 @@ function maybeSendNotification(numErrors: number) {
|
|
|
75
69
|
|
|
76
70
|
export const DynamicFavicon = (props: Props) => {
|
|
77
71
|
const { isRunning } = props;
|
|
78
|
-
const
|
|
72
|
+
const numErrors = useAtomValue(cellErrorCount);
|
|
79
73
|
|
|
80
74
|
useEffect(() => {
|
|
81
75
|
const favicon = getOrCreateFavicon();
|
|
@@ -86,46 +80,33 @@ export const DynamicFavicon = (props: Props) => {
|
|
|
86
80
|
return;
|
|
87
81
|
}
|
|
88
82
|
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
// When notebook is running, display running favicon
|
|
92
|
-
if (isRunning) {
|
|
93
|
-
key = "running";
|
|
94
|
-
} else {
|
|
95
|
-
// When run is complete, display success or error favicon
|
|
96
|
-
key = errors.length === 0 ? "success" : "error";
|
|
97
|
-
}
|
|
98
|
-
favicon.href = await getFaviconUrl(key);
|
|
83
|
+
const key = isRunning ? "running" : numErrors === 0 ? "success" : "error";
|
|
84
|
+
favicon.href = FAVICON_PATHS[key];
|
|
99
85
|
|
|
100
|
-
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const timeoutId = setTimeout(async () => {
|
|
107
|
-
favicon.href = await getFaviconUrl("idle");
|
|
108
|
-
}, 3000);
|
|
109
|
-
|
|
110
|
-
return () => clearTimeout(timeoutId);
|
|
111
|
-
};
|
|
86
|
+
if (!document.hasFocus()) {
|
|
87
|
+
// The focus listener resets the favicon when the user returns.
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
112
90
|
|
|
113
|
-
|
|
114
|
-
|
|
91
|
+
const timeoutId = setTimeout(() => {
|
|
92
|
+
favicon.href = FAVICON_PATHS.idle;
|
|
93
|
+
}, 3000);
|
|
94
|
+
return () => clearTimeout(timeoutId);
|
|
95
|
+
}, [isRunning, numErrors]);
|
|
115
96
|
|
|
116
97
|
// Send user notification when run has completed
|
|
117
98
|
const prevRunning = usePrevious(isRunning) ?? isRunning;
|
|
118
99
|
useEffect(() => {
|
|
119
100
|
if (prevRunning && !isRunning) {
|
|
120
|
-
maybeSendNotification(
|
|
101
|
+
maybeSendNotification(numErrors);
|
|
121
102
|
}
|
|
122
|
-
}, [
|
|
103
|
+
}, [numErrors, prevRunning, isRunning]);
|
|
123
104
|
|
|
124
105
|
// When notebook comes back in focus, reset favicon
|
|
125
|
-
useEventListener(window, "focus",
|
|
106
|
+
useEventListener(window, "focus", () => {
|
|
126
107
|
if (!isRunning) {
|
|
127
108
|
const favicon = getOrCreateFavicon();
|
|
128
|
-
favicon.href =
|
|
109
|
+
favicon.href = FAVICON_PATHS.idle;
|
|
129
110
|
}
|
|
130
111
|
});
|
|
131
112
|
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
screen,
|
|
9
9
|
waitFor,
|
|
10
10
|
} from "@testing-library/react";
|
|
11
|
-
import { createStore, Provider, useAtomValue } from "jotai";
|
|
11
|
+
import { atom, createStore, Provider, useAtomValue } from "jotai";
|
|
12
12
|
import type React from "react";
|
|
13
13
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
14
14
|
import { MockNotebook } from "@/__mocks__/notebook";
|
|
@@ -16,7 +16,7 @@ import { cellId } from "@/__tests__/branded";
|
|
|
16
16
|
import { PAIR_CONFIG } from "@/__tests__/fixtures/pair-config";
|
|
17
17
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
18
18
|
import { toast } from "@/components/ui/use-toast";
|
|
19
|
-
import {
|
|
19
|
+
import type { Attachment } from "@/core/attachments/state";
|
|
20
20
|
import { exportedForTesting, notebookAtom } from "@/core/cells/cells";
|
|
21
21
|
import {
|
|
22
22
|
type CellOutputMessage,
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from "@/core/cells/error-report";
|
|
26
26
|
import { pairConfigAtom } from "@/core/config/pair";
|
|
27
27
|
import { notebookStateFromSession } from "@/core/cells/session";
|
|
28
|
+
import { createNotebookClient } from "@/core/host/notebook-client";
|
|
28
29
|
import type { MarimoError, OutputMessage } from "@/core/kernel/messages";
|
|
29
30
|
import {
|
|
30
31
|
type StableSessionId,
|
|
@@ -32,7 +33,8 @@ import {
|
|
|
32
33
|
} from "@/core/kernel/session";
|
|
33
34
|
import { API } from "@/core/network/api";
|
|
34
35
|
import { connectionAtom } from "@/core/network/connection";
|
|
35
|
-
import {
|
|
36
|
+
import { notebookClientAtom } from "@/core/websocket/state";
|
|
37
|
+
import { ConnectionState } from "@/core/websocket/types";
|
|
36
38
|
import { Deferred } from "@/utils/Deferred";
|
|
37
39
|
import { HTTPError } from "@/utils/errors";
|
|
38
40
|
import { MarimoErrorOutput } from "../../output/MarimoErrorOutput";
|
|
@@ -48,6 +50,8 @@ vi.mock("@/components/editor/chrome/wrapper/useOpenAiAssistant", () => ({
|
|
|
48
50
|
|
|
49
51
|
const CELL_ID = cellId("test-cell");
|
|
50
52
|
const STABLE_ID = "stable/test" as StableSessionId;
|
|
53
|
+
const attachmentsAtom = atom<Attachment[]>([]);
|
|
54
|
+
|
|
51
55
|
const AGENT: Attachment = {
|
|
52
56
|
id: "a1",
|
|
53
57
|
kind: "agent",
|
|
@@ -93,6 +97,11 @@ function renderButton({
|
|
|
93
97
|
const store = createStore();
|
|
94
98
|
store.set(connectionAtom, { state: ConnectionState.OPEN });
|
|
95
99
|
store.set(pairConfigAtom, { ...PAIR_CONFIG, hostEnabled: preview });
|
|
100
|
+
store.set(notebookClientAtom, {
|
|
101
|
+
...createNotebookClient(store).client,
|
|
102
|
+
connectionAtom: atom({ state: ConnectionState.OPEN }),
|
|
103
|
+
attachmentsAtom,
|
|
104
|
+
});
|
|
96
105
|
store.set(attachmentsAtom, attachments);
|
|
97
106
|
store.set(stableSessionIdAtom, stableId);
|
|
98
107
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
@@ -177,26 +186,6 @@ describe("SendErrorReportButton", () => {
|
|
|
177
186
|
expect(screen.getByRole("button")).toBeDisabled();
|
|
178
187
|
});
|
|
179
188
|
|
|
180
|
-
it.each([
|
|
181
|
-
{ state: ConnectionState.CONNECTING },
|
|
182
|
-
{ state: ConnectionState.CLOSING },
|
|
183
|
-
{ state: ConnectionState.NOT_STARTED },
|
|
184
|
-
{
|
|
185
|
-
state: ConnectionState.CLOSED,
|
|
186
|
-
code: WebSocketClosedReason.KERNEL_DISCONNECTED,
|
|
187
|
-
reason: "offline",
|
|
188
|
-
},
|
|
189
|
-
])(
|
|
190
|
-
"does not send from a stale snapshot while the browser is %j",
|
|
191
|
-
(connection) => {
|
|
192
|
-
const { store } = renderButton();
|
|
193
|
-
act(() => store.set(connectionAtom, connection));
|
|
194
|
-
expect(screen.getByRole("button")).toBeDisabled();
|
|
195
|
-
send();
|
|
196
|
-
expect(API.post).not.toHaveBeenCalled();
|
|
197
|
-
},
|
|
198
|
-
);
|
|
199
|
-
|
|
200
189
|
it.each([
|
|
201
190
|
[new HTTPError(409, "Conflict"), "No agent attached"],
|
|
202
191
|
[new HTTPError(500, "Server error"), "Could not send error"],
|
|
@@ -137,5 +137,11 @@ describe("FileExplorer upload destination", () => {
|
|
|
137
137
|
}),
|
|
138
138
|
).toBeVisible();
|
|
139
139
|
expect(screen.queryByText(".hidden")).not.toBeInTheDocument();
|
|
140
|
+
|
|
141
|
+
fireEvent.click(screen.getByTestId("file-explorer-hidden-files-button"));
|
|
142
|
+
expect(await screen.findByText(".hidden")).toBeVisible();
|
|
143
|
+
expect(
|
|
144
|
+
screen.getByRole("button", { name: "Upload files to workspace" }),
|
|
145
|
+
).toBeVisible();
|
|
140
146
|
});
|
|
141
147
|
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { act, renderHook, waitFor } from "@testing-library/react";
|
|
4
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import type { EditRequests } from "@/core/network/types";
|
|
6
|
+
import { Deferred } from "@/utils/Deferred";
|
|
7
|
+
import { RequestingTree } from "../requesting-tree";
|
|
8
|
+
import { useFileSearch } from "../use-file-search";
|
|
9
|
+
|
|
10
|
+
const sendSearchFiles = vi.hoisted(() =>
|
|
11
|
+
vi.fn<EditRequests["sendSearchFiles"]>(),
|
|
12
|
+
);
|
|
13
|
+
vi.mock("@/core/network/requests", () => ({
|
|
14
|
+
useRequestClient: () => ({ sendSearchFiles }),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
describe("useFileSearch", () => {
|
|
18
|
+
let tree: RequestingTree;
|
|
19
|
+
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
sendSearchFiles.mockReset();
|
|
22
|
+
tree = new RequestingTree({
|
|
23
|
+
getRoots: vi.fn().mockResolvedValue({
|
|
24
|
+
roots: [{ path: "/root", name: "workspace", isPrimary: true }],
|
|
25
|
+
}),
|
|
26
|
+
listFiles: vi.fn().mockResolvedValue({ files: [], root: "/root" }),
|
|
27
|
+
createFileOrFolder: vi.fn(),
|
|
28
|
+
deleteFileOrFolder: vi.fn(),
|
|
29
|
+
copyFileOrFolder: vi.fn(),
|
|
30
|
+
renameFileOrFolder: vi.fn(),
|
|
31
|
+
});
|
|
32
|
+
await tree.initialize(() => undefined);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("derives loading for a refetch and a changed hidden-file filter", async () => {
|
|
36
|
+
sendSearchFiles.mockResolvedValue({
|
|
37
|
+
files: [],
|
|
38
|
+
query: "file",
|
|
39
|
+
totalFound: 0,
|
|
40
|
+
});
|
|
41
|
+
const { result, rerender } = renderHook(
|
|
42
|
+
({ showHiddenFiles }) =>
|
|
43
|
+
useFileSearch({ query: "file", tree, showHiddenFiles }),
|
|
44
|
+
{ initialProps: { showHiddenFiles: false } },
|
|
45
|
+
);
|
|
46
|
+
await waitFor(() => expect(result.current.state.status).toBe("success"));
|
|
47
|
+
|
|
48
|
+
const pending = new Deferred<
|
|
49
|
+
Awaited<ReturnType<EditRequests["sendSearchFiles"]>>
|
|
50
|
+
>();
|
|
51
|
+
sendSearchFiles.mockReturnValue(pending.promise);
|
|
52
|
+
act(() => result.current.refetch());
|
|
53
|
+
expect(result.current.state.status).toBe("loading");
|
|
54
|
+
await act(async () =>
|
|
55
|
+
pending.resolve({ files: [], query: "file", totalFound: 0 }),
|
|
56
|
+
);
|
|
57
|
+
expect(result.current.state.status).toBe("success");
|
|
58
|
+
|
|
59
|
+
sendSearchFiles.mockReturnValue(new Promise(() => undefined));
|
|
60
|
+
rerender({ showHiddenFiles: true });
|
|
61
|
+
expect(result.current.state.status).toBe("loading");
|
|
62
|
+
await waitFor(() =>
|
|
63
|
+
expect(sendSearchFiles).toHaveBeenLastCalledWith(
|
|
64
|
+
expect.objectContaining({ includeHidden: true }),
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("keeps superseded scans serial and ignores their results", async () => {
|
|
70
|
+
const first = new Deferred<
|
|
71
|
+
Awaited<ReturnType<EditRequests["sendSearchFiles"]>>
|
|
72
|
+
>();
|
|
73
|
+
sendSearchFiles
|
|
74
|
+
.mockReturnValueOnce(first.promise)
|
|
75
|
+
.mockResolvedValue({ files: [], query: "file", totalFound: 0 });
|
|
76
|
+
const { result } = renderHook(() =>
|
|
77
|
+
useFileSearch({ query: "file", tree, showHiddenFiles: false }),
|
|
78
|
+
);
|
|
79
|
+
await waitFor(() => expect(sendSearchFiles).toHaveBeenCalledOnce());
|
|
80
|
+
|
|
81
|
+
act(() => result.current.refetch());
|
|
82
|
+
expect(sendSearchFiles).toHaveBeenCalledOnce();
|
|
83
|
+
expect(result.current.state.status).toBe("loading");
|
|
84
|
+
await act(async () =>
|
|
85
|
+
first.resolve({ files: [], query: "file", totalFound: 0 }),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
expect(sendSearchFiles).toHaveBeenCalledTimes(2);
|
|
89
|
+
expect(result.current.state.status).toBe("success");
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -201,11 +201,16 @@ export const FileExplorer: React.FC<{
|
|
|
201
201
|
() => filterHiddenTree(data, showHiddenFiles),
|
|
202
202
|
[data, showHiddenFiles],
|
|
203
203
|
);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
204
|
+
const selectedFolderId = selectedFolder?.id;
|
|
205
|
+
const selectedFolderVisible = React.useMemo(
|
|
206
|
+
() =>
|
|
207
|
+
selectedFolderId === undefined ||
|
|
208
|
+
treeContainsId(visibleData, selectedFolderId),
|
|
209
|
+
[visibleData, selectedFolderId],
|
|
210
|
+
);
|
|
211
|
+
if (!selectedFolderVisible) {
|
|
212
|
+
setSelectedFolder(null);
|
|
213
|
+
}
|
|
209
214
|
const contextValue = React.useMemo<FileExplorerContextValue>(
|
|
210
215
|
() => ({
|
|
211
216
|
tree,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
SaveIcon,
|
|
10
10
|
} from "lucide-react";
|
|
11
11
|
import type React from "react";
|
|
12
|
-
import { useEffect, useRef, useState } from "react";
|
|
12
|
+
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
13
13
|
import { renderShortcut } from "@/components/shortcuts/renderShortcut";
|
|
14
14
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
15
15
|
import { Button } from "@/components/ui/button";
|
|
@@ -82,7 +82,9 @@ export const FileViewer: React.FC<Props> = ({ file, onOpenNotebook }) => {
|
|
|
82
82
|
// carry no draft to preserve.
|
|
83
83
|
// We use a ref for internalValue so we don't call this effect on each keystroke
|
|
84
84
|
const internalValueRef = useRef<string>(internalValue);
|
|
85
|
-
|
|
85
|
+
useLayoutEffect(() => {
|
|
86
|
+
internalValueRef.current = internalValue;
|
|
87
|
+
}, [internalValue]);
|
|
86
88
|
useEffect(() => {
|
|
87
89
|
return () => {
|
|
88
90
|
if (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { useRequestClient } from "@/core/network/requests";
|
|
5
5
|
import { useDebounce } from "@/hooks/useDebounce";
|
|
6
6
|
import {
|
|
@@ -34,20 +34,29 @@ export function useFileSearch({
|
|
|
34
34
|
}): { state: FileSearchState; refetch: () => void } {
|
|
35
35
|
const { sendSearchFiles } = useRequestClient();
|
|
36
36
|
const debouncedQuery = useDebounce(query, 300);
|
|
37
|
-
|
|
37
|
+
// Nullable so the ref is not built on every render
|
|
38
|
+
const pending = useRef<Promise<void> | null>(null);
|
|
38
39
|
const [revision, setRevision] = useState(0);
|
|
39
40
|
const refetch = useCallback(() => setRevision((value) => value + 1), []);
|
|
40
|
-
const
|
|
41
|
+
const requestIdentity = useMemo(
|
|
42
|
+
() => ({ query, tree, showHiddenFiles, revision, sendSearchFiles }),
|
|
43
|
+
[query, tree, showHiddenFiles, revision, sendSearchFiles],
|
|
44
|
+
);
|
|
45
|
+
const [result, setResult] = useState<{
|
|
46
|
+
requestIdentity: typeof requestIdentity;
|
|
47
|
+
state: FileSearchState;
|
|
48
|
+
}>();
|
|
41
49
|
|
|
42
50
|
useEffect(() => {
|
|
51
|
+
// Read inputs through requestIdentity so it alone drives new requests
|
|
52
|
+
const { query, tree, showHiddenFiles, sendSearchFiles } = requestIdentity;
|
|
43
53
|
let cancelled = false;
|
|
44
54
|
if (!query || query !== debouncedQuery) {
|
|
45
55
|
return;
|
|
46
56
|
}
|
|
47
|
-
setResult({ status: "loading", query });
|
|
48
57
|
// Let the in-flight server scan finish before starting another. Superseded
|
|
49
58
|
// queued queries and remaining roots are skipped, keeping scan work bounded.
|
|
50
|
-
const request = pending.current.then(async () => {
|
|
59
|
+
const request = (pending.current ?? Promise.resolve()).then(async () => {
|
|
51
60
|
const filesByPath = new Map<string, FileTreeNode>();
|
|
52
61
|
let rootLimitReached = false;
|
|
53
62
|
let failedRootCount = 0;
|
|
@@ -88,9 +97,12 @@ export function useFileSearch({
|
|
|
88
97
|
if (!cancelled) {
|
|
89
98
|
if (roots.length > 0 && failedRootCount === roots.length) {
|
|
90
99
|
setResult({
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
100
|
+
requestIdentity,
|
|
101
|
+
state: {
|
|
102
|
+
status: "error",
|
|
103
|
+
query,
|
|
104
|
+
error: new Error("Could not search any root"),
|
|
105
|
+
},
|
|
94
106
|
});
|
|
95
107
|
return;
|
|
96
108
|
}
|
|
@@ -110,33 +122,35 @@ export function useFileSearch({
|
|
|
110
122
|
rank(left) - rank(right) || left.name.localeCompare(right.name),
|
|
111
123
|
);
|
|
112
124
|
setResult({
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
125
|
+
requestIdentity,
|
|
126
|
+
state: {
|
|
127
|
+
status: "success",
|
|
128
|
+
isPartial: failedRootCount > 0,
|
|
129
|
+
query,
|
|
130
|
+
files: sortedFiles.slice(0, RESULT_LIMIT),
|
|
131
|
+
hasMore: rootLimitReached || sortedFiles.length > RESULT_LIMIT,
|
|
132
|
+
},
|
|
118
133
|
});
|
|
119
134
|
}
|
|
120
135
|
});
|
|
121
136
|
pending.current = request.catch((error: unknown) => {
|
|
122
137
|
if (!cancelled) {
|
|
123
|
-
setResult({
|
|
138
|
+
setResult({
|
|
139
|
+
requestIdentity,
|
|
140
|
+
state: { status: "error", query, error },
|
|
141
|
+
});
|
|
124
142
|
}
|
|
125
143
|
});
|
|
126
144
|
return () => {
|
|
127
145
|
cancelled = true;
|
|
128
146
|
};
|
|
129
|
-
}, [
|
|
147
|
+
}, [debouncedQuery, requestIdentity]);
|
|
130
148
|
|
|
131
149
|
if (!query) {
|
|
132
150
|
return { state: { status: "idle" }, refetch };
|
|
133
151
|
}
|
|
134
|
-
if (
|
|
135
|
-
query !== debouncedQuery ||
|
|
136
|
-
result.status === "idle" ||
|
|
137
|
-
result.query !== query
|
|
138
|
-
) {
|
|
152
|
+
if (query !== debouncedQuery || result?.requestIdentity !== requestIdentity) {
|
|
139
153
|
return { state: { status: "loading", query }, refetch };
|
|
140
154
|
}
|
|
141
|
-
return { state: result, refetch };
|
|
155
|
+
return { state: result.state, refetch };
|
|
142
156
|
}
|
|
@@ -248,7 +248,7 @@ export const replaceTracebackFilenames = (domNode: DOMNode) => {
|
|
|
248
248
|
<Tooltip content={tooltipContent}>
|
|
249
249
|
<button
|
|
250
250
|
type="button"
|
|
251
|
-
className="ml-1 p-1 rounded-sm hover:bg-muted transition-
|
|
251
|
+
className="ml-1 p-1 rounded-sm hover:bg-muted transition-colors inline"
|
|
252
252
|
>
|
|
253
253
|
<BugPlayIcon
|
|
254
254
|
onClick={() => {
|
|
@@ -274,7 +274,7 @@ export const replaceTracebackFilenames = (domNode: DOMNode) => {
|
|
|
274
274
|
return (
|
|
275
275
|
<div
|
|
276
276
|
className="inline-block cursor-pointer text-destructive hover:underline"
|
|
277
|
-
onClick={(
|
|
277
|
+
onClick={() => {
|
|
278
278
|
getRequestClient().openFile({
|
|
279
279
|
path: info.filePath,
|
|
280
280
|
lineNumber: info.lineNumber,
|
|
@@ -358,6 +358,7 @@ export class AnsiReducer {
|
|
|
358
358
|
|
|
359
359
|
export type StringOutputMessage = Omit<OutputMessage, "data"> & {
|
|
360
360
|
data: string;
|
|
361
|
+
id?: number;
|
|
361
362
|
};
|
|
362
363
|
|
|
363
364
|
/**
|
|
@@ -369,6 +370,7 @@ export class StatefulOutputMessage implements OutputMessage {
|
|
|
369
370
|
public readonly channel: OutputMessage["channel"];
|
|
370
371
|
public readonly timestamp: OutputMessage["timestamp"];
|
|
371
372
|
public readonly code: OutputMessage["code"];
|
|
373
|
+
public readonly id: StringOutputMessage["id"];
|
|
372
374
|
private ansiReducer = new AnsiReducer();
|
|
373
375
|
private _data: string;
|
|
374
376
|
|
|
@@ -385,6 +387,7 @@ export class StatefulOutputMessage implements OutputMessage {
|
|
|
385
387
|
message.timestamp,
|
|
386
388
|
ansiReducer,
|
|
387
389
|
message.code,
|
|
390
|
+
message.id,
|
|
388
391
|
);
|
|
389
392
|
}
|
|
390
393
|
|
|
@@ -394,11 +397,13 @@ export class StatefulOutputMessage implements OutputMessage {
|
|
|
394
397
|
timestamp: OutputMessage["timestamp"],
|
|
395
398
|
ansiReducer: AnsiReducer,
|
|
396
399
|
code: OutputMessage["code"],
|
|
400
|
+
id: StringOutputMessage["id"],
|
|
397
401
|
) {
|
|
398
402
|
this.mimetype = mimetype;
|
|
399
403
|
this.channel = channel;
|
|
400
404
|
this.timestamp = timestamp;
|
|
401
405
|
this.code = code;
|
|
406
|
+
this.id = id;
|
|
402
407
|
this.ansiReducer = ansiReducer;
|
|
403
408
|
this._data = this.ansiReducer.render();
|
|
404
409
|
}
|
|
@@ -411,6 +416,7 @@ export class StatefulOutputMessage implements OutputMessage {
|
|
|
411
416
|
this.timestamp,
|
|
412
417
|
this.ansiReducer,
|
|
413
418
|
this.code,
|
|
419
|
+
this.id,
|
|
414
420
|
);
|
|
415
421
|
}
|
|
416
422
|
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
ChevronsUpDownIcon,
|
|
7
7
|
WrapTextIcon,
|
|
8
8
|
} from "lucide-react";
|
|
9
|
-
import React, { useEffect, useLayoutEffect,
|
|
9
|
+
import React, { useEffect, useLayoutEffect, useState } from "react";
|
|
10
10
|
import { ToggleButton } from "react-aria-components";
|
|
11
11
|
import { DebuggerControls } from "@/components/debugger/debugger-code";
|
|
12
12
|
import { CopyClipboardIcon } from "@/components/icons/copy-icon";
|
|
@@ -16,8 +16,7 @@ import { Tooltip } from "@/components/ui/tooltip";
|
|
|
16
16
|
import type { CellId } from "@/core/cells/ids";
|
|
17
17
|
import { isInternalCellName } from "@/core/cells/names";
|
|
18
18
|
import { useExpandedConsoleOutput } from "@/core/cells/outputs";
|
|
19
|
-
import type {
|
|
20
|
-
import type { OutputMessage } from "@/core/kernel/messages";
|
|
19
|
+
import type { ConsoleOutputMessage } from "@/core/cells/types";
|
|
21
20
|
import {
|
|
22
21
|
type UseInputHistoryReturn,
|
|
23
22
|
useInputHistory,
|
|
@@ -49,31 +48,21 @@ export const CONSOLE_CLEAR_DEBOUNCE_MS = 200;
|
|
|
49
48
|
*/
|
|
50
49
|
function useDebouncedConsoleOutputs<T>(outputs: T[]): T[] {
|
|
51
50
|
const [debouncedOutputs, setDebouncedOutputs] = useState(outputs);
|
|
52
|
-
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
53
51
|
|
|
54
|
-
// Non-empty outputs: apply immediately
|
|
52
|
+
// Non-empty outputs: apply immediately. Effect cleanup cancels pending clears.
|
|
55
53
|
if (outputs.length > 0 && debouncedOutputs !== outputs) {
|
|
56
|
-
if (timerRef.current !== null) {
|
|
57
|
-
clearTimeout(timerRef.current);
|
|
58
|
-
timerRef.current = null;
|
|
59
|
-
}
|
|
60
54
|
setDebouncedOutputs(outputs);
|
|
61
55
|
}
|
|
62
56
|
|
|
63
57
|
// Empty outputs: delay the clear so new outputs can arrive first
|
|
64
58
|
useEffect(() => {
|
|
65
|
-
if (outputs.length
|
|
66
|
-
|
|
67
|
-
timerRef.current = null;
|
|
68
|
-
setDebouncedOutputs([]);
|
|
69
|
-
}, CONSOLE_CLEAR_DEBOUNCE_MS);
|
|
59
|
+
if (outputs.length > 0) {
|
|
60
|
+
return;
|
|
70
61
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
};
|
|
62
|
+
const timer = setTimeout(() => {
|
|
63
|
+
setDebouncedOutputs([]);
|
|
64
|
+
}, CONSOLE_CLEAR_DEBOUNCE_MS);
|
|
65
|
+
return () => clearTimeout(timer);
|
|
77
66
|
}, [outputs]);
|
|
78
67
|
|
|
79
68
|
return debouncedOutputs;
|
|
@@ -83,7 +72,7 @@ interface Props {
|
|
|
83
72
|
cellId: CellId;
|
|
84
73
|
cellName: string;
|
|
85
74
|
className?: string;
|
|
86
|
-
consoleOutputs:
|
|
75
|
+
consoleOutputs: ConsoleOutputMessage[];
|
|
87
76
|
stale: boolean;
|
|
88
77
|
interrupted: boolean;
|
|
89
78
|
debuggerActive: boolean;
|
|
@@ -251,19 +240,24 @@ const ConsoleOutputInternal = (props: Props): React.ReactNode => {
|
|
|
251
240
|
return null;
|
|
252
241
|
}
|
|
253
242
|
|
|
243
|
+
const originalIdx = consoleOutputs.length - idx - 1;
|
|
244
|
+
// Outputs render in reverse, so the reversed index shifts on every
|
|
245
|
+
// new line. The assigned id survives collapse and truncation. The
|
|
246
|
+
// original position is the fallback for outputs without one.
|
|
247
|
+
const key = output.id ?? `position-${originalIdx}`;
|
|
248
|
+
|
|
254
249
|
if (output.channel === "stdin") {
|
|
255
250
|
invariant(
|
|
256
251
|
typeof output.data === "string",
|
|
257
252
|
"Expected data to be a string",
|
|
258
253
|
);
|
|
259
254
|
|
|
260
|
-
const originalIdx = consoleOutputs.length - idx - 1;
|
|
261
255
|
const isPassword = output.mimetype === "text/password";
|
|
262
256
|
|
|
263
257
|
if (output.response == null && lastStdInputIdx === idx) {
|
|
264
258
|
return (
|
|
265
259
|
<StdInput
|
|
266
|
-
key={
|
|
260
|
+
key={key}
|
|
267
261
|
output={output.data}
|
|
268
262
|
isPdb={isPdb}
|
|
269
263
|
isPassword={isPassword}
|
|
@@ -278,7 +272,7 @@ const ConsoleOutputInternal = (props: Props): React.ReactNode => {
|
|
|
278
272
|
|
|
279
273
|
return (
|
|
280
274
|
<StdInputWithResponse
|
|
281
|
-
key={
|
|
275
|
+
key={key}
|
|
282
276
|
output={output.data}
|
|
283
277
|
response={output.response}
|
|
284
278
|
isPassword={isPassword}
|
|
@@ -288,7 +282,7 @@ const ConsoleOutputInternal = (props: Props): React.ReactNode => {
|
|
|
288
282
|
}
|
|
289
283
|
|
|
290
284
|
return (
|
|
291
|
-
<React.Fragment key={
|
|
285
|
+
<React.Fragment key={key}>
|
|
292
286
|
<OutputRenderer
|
|
293
287
|
cellId={cellId}
|
|
294
288
|
onRefactorWithAI={onRefactorWithAI}
|