@marimo-team/islands 0.25.3-dev5 → 0.25.3-dev51
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-Da5OoEUL.js} +14 -14
- package/dist/{ErrorBoundary-BiZWOrv4.js → ErrorBoundary-CbxCC2kk.js} +8 -8
- package/dist/{abnfDiagram-VCTEODGH-D-Ez91gV.js → abnfDiagram-VCTEODGH-Bz3mrprX.js} +2 -2
- package/dist/any-language-editor-k2B8YI3Y.js +75 -0
- package/dist/{architectureDiagram-5GKGNRK7-CTpcNtO8.js → architectureDiagram-5GKGNRK7-Cs5ujb9K.js} +1 -1
- package/dist/{arrays-CN1djC4k.js → arrays-R6JsImiT.js} +8 -0
- 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-C1Gjgokv.js} +1103 -1103
- package/dist/{check-EDwSwP89.js → check-P35dc2dH.js} +1 -1
- package/dist/{chunk-IMKFNOWR-DGxi-zeo.js → chunk-IMKFNOWR-CcTSaxZt.js} +1 -1
- package/dist/{chunk-SHT3W25Y-CV9J3rAB.js → chunk-SHT3W25Y-DcJcjOaO.js} +1 -1
- package/dist/{chunk-SVP7TREG-BL-9Fqgg.js → chunk-SVP7TREG-BVtDTjaF.js} +1 -1
- package/dist/{chunk-TICWLB2K-Cu_XMjDM.js → chunk-TICWLB2K-BkDFkh8q.js} +1 -1
- package/dist/{chunk-YOKDWASO-DPZk8vhS.js → chunk-YOKDWASO-DKplTrBY.js} +20 -20
- package/dist/{classDiagram-ZZMXUADV-BXXV13Hq.js → classDiagram-ZZMXUADV-CfAt7HT5.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-B7FXJ1Cp.js → classDiagram-v2-VYDZK3BY-CGEjNYa2.js} +2 -2
- package/dist/{code-visibility-CW5_8ZOI.js → code-visibility-DoZ9lxzx.js} +6 -6
- package/dist/{common-mzWw4FdI.js → common-D1PacyXO.js} +3112 -3015
- package/dist/{copy-icon-Ft7t9bqz.js → copy-icon-CPoxUG7G.js} +3 -3
- package/dist/{cynefinDiagram-5FMLGOSQ-CH96rwba.js → cynefinDiagram-5FMLGOSQ-BW6NmfJi.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-BD3PmpFY.js → dagre-GXQ25YYZ-CWg3CTuF.js} +1 -1
- package/dist/{data-grid-overlay-editor-CJmehSdc.js → data-grid-overlay-editor-B50UpkXd.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-BPxtoS-z.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-muQtNZc4.js → diagram-UQ7AKVKN-DA-vJdFL.js} +1 -1
- package/dist/{diagram-VX7I27RA-CdhuZAqR.js → diagram-VX7I27RA-D6bkR84S.js} +1 -1
- package/dist/{diagram-Z3DM3KII-BD8mXjOj.js → diagram-Z3DM3KII-m60g4cAT.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-MG5hiCIa.js} +76 -76
- package/dist/{ebnfDiagram-PWID7BFC-BIN1sweE.js → ebnfDiagram-PWID7BFC-CK99sho2.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-Ctp5cfoN.js → erDiagram-RLTQ6QDP-Bryq0tdE.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-CMNtA8NK.js} +959 -959
- package/dist/{feedback-button-BcT1FBud.js → feedback-button-DqPqwryB.js} +29 -29
- package/dist/{flowDiagram-HODETNUW-3EfLrlFb.js → flowDiagram-HODETNUW-D-3aRpfS.js} +2 -2
- package/dist/{formats-CeOmU1MU.js → formats-6pLWWrY2.js} +3 -3
- package/dist/{ganttDiagram-EL5Y4UJY-BQT16JSc.js → ganttDiagram-EL5Y4UJY-Dh_4cBJZ.js} +2 -2
- package/dist/{glide-data-editor-XckatD0j.js → glide-data-editor-BxMhUd3K.js} +370 -368
- package/dist/{glide-utils-DJyqh0EM.js → glide-utils-Bt8_pBa2.js} +16 -16
- package/dist/{globals-jRrLCkrP.js → globals-DTJa_doJ.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-By72ALyI.js} +29 -29
- package/dist/{infoDiagram-27XIBGKW-DipQoHxl.js → infoDiagram-27XIBGKW-s1C1kgfU.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-DgFMjSGz.js → ishikawaDiagram-5VMMS53U-CCsRgolY.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-w1ACVjgl.js → kanban-definition-UXKFOSKX-BrmxDlcc.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 +2358 -2221
- package/dist/{mermaid-CHo49Yv-.js → mermaid-DKI0869I.js} +38 -38
- package/dist/mermaid-HWGCJPDP-Du3-HPQA.js +2 -0
- package/dist/{mindmap-definition-YA3MSWOX-CrT0KSfX.js → mindmap-definition-YA3MSWOX-641xJysF.js} +1 -1
- package/dist/{number-overlay-editor-pZx8dUDV.js → number-overlay-editor-Z_H6x5b-.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-keVUAUeS.js} +2 -2
- package/dist/{pieDiagram-E7YTZNPT-BfxJ_XCc.js → pieDiagram-E7YTZNPT-DHktXPO_.js} +1 -1
- package/dist/precisionRound-DhAzeRcm.js +49 -0
- package/dist/{process-output-CKRXyszb.js → process-output-D-OMW5sV.js} +4 -4
- package/dist/{quadrantDiagram-AXDQQJYC-Ccw0xMjN.js → quadrantDiagram-AXDQQJYC-Blrm7V7_.js} +1 -1
- package/dist/{railroadDiagram-O6MQD6OU-8vFLVTPu.js → railroadDiagram-O6MQD6OU-BVEF4OV_.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-CWo-UAKV.js} +1 -1
- package/dist/{reveal-component-79ez8-D5.js → reveal-component-tAB5K3QE.js} +10 -10
- package/dist/{select-BButFi-d.js → select-B0LRDH0g.js} +4 -4
- package/dist/{sequenceDiagram-WJ2MYXX4-CXFMnxGh.js → sequenceDiagram-WJ2MYXX4-CHQvLnq4.js} +1 -1
- package/dist/{spec-BwarBpUJ.js → spec-C-LRKGtT.js} +4 -4
- package/dist/{stateDiagram-D77RDMKH-DQeA_CwI.js → stateDiagram-D77RDMKH-BOJZgbUJ.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-BxxQucm1.js → stateDiagram-v2-MP3YSRHH-C53XXdYH.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/{swimlanes-42K2YHIH-Do6L-cYg.js → swimlanes-42K2YHIH-D2o1gOtw.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-DUzPvSVU.js → swimlanesDiagram-VR7AAH4N-BHFzN1Wk.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-rv7QUa46.js} +1 -1
- package/dist/{toDate-vIXljnsr.js → toDate-CjPuEPou.js} +21 -20
- 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-ChgNtdmE.js} +15 -14
- package/dist/{useDateFormatter-D8XruNGG.js → useDateFormatter-c_yJtkAe.js} +2 -2
- package/dist/{useDeepCompareMemoize-DXwzRINI.js → useDeepCompareMemoize-Bet7kSsI.js} +1 -1
- package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities-BkdeVdXC.js} +1 -1
- package/dist/{useLifecycle-j7D9uD1o.js → useLifecycle-VVmz_24o.js} +3 -3
- package/dist/{useTheme-CRnjkku0.js → useTheme-DympCsQg.js} +5 -5
- package/dist/{vega-component-DTG6zxaY.js → vega-component-DnfQeoyt.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-D36Zk7t-.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-48cUWkfA.js → wardleyDiagram-VM6X3IG4-BLBolvyQ.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-CswYgYeu.js → xychartDiagram-S5SC5T6Z-CfH57l0v.js} +2 -2
- package/package.json +4 -4
- package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
- package/src/__tests__/host-imports.test.ts +1 -0
- package/src/__tests__/mount.test.ts +8 -8
- package/src/components/app-config/ai-config.tsx +5 -1
- package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
- package/src/components/chat/chat-panel.tsx +3 -7
- package/src/components/chat/chat-utils.ts +4 -8
- 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 +417 -6
- package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
- package/src/components/data-table/columns.tsx +2 -1
- package/src/components/data-table/context-menu.tsx +36 -20
- package/src/components/data-table/date-popover.tsx +6 -4
- package/src/components/data-table/export-actions.tsx +263 -161
- package/src/components/data-table/row-viewer-panel/row-viewer.tsx +2 -2
- package/src/components/data-table/schemas.ts +14 -36
- package/src/components/editor/Output.tsx +5 -1
- package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
- package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +80 -252
- package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +186 -34
- package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
- package/src/components/editor/actions/pair-with-agent-modal.tsx +96 -36
- package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
- package/src/components/editor/cell/CreateCellButton.tsx +8 -10
- 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/chrome/wrapper/__tests__/attachment-status.test.tsx +277 -78
- package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +196 -41
- 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 +139 -36
- package/src/components/editor/errors/send-error-report-button.tsx +10 -7
- package/src/components/editor/file-tree/file-viewer.tsx +4 -2
- package/src/components/editor/output/MarimoTracebackOutput.tsx +28 -22
- package/src/components/editor/output/console/ConsoleOutput.tsx +8 -18
- package/src/components/pages/home-page.tsx +4 -4
- package/src/components/slides/slide-cell-view.tsx +1 -1
- package/src/components/tracing/tracing.tsx +2 -2
- package/src/core/attachments/__tests__/toast.test.tsx +58 -0
- package/src/core/attachments/state.ts +5 -8
- package/src/core/attachments/toast.ts +15 -0
- package/src/core/cells/__tests__/error-report.test.ts +53 -3
- package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
- package/src/core/cells/__tests__/session.test.ts +18 -3
- package/src/core/cells/cells.ts +2 -1
- package/src/core/cells/error-report.ts +14 -6
- package/src/core/cells/pending-delete-service.ts +2 -6
- package/src/core/cells/session.ts +19 -1
- package/src/core/config/pair.ts +5 -3
- 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/edit-app.tsx +2 -2
- package/src/core/host/AGENTS.md +11 -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__/events.test.ts +49 -0
- package/src/core/host/__tests__/fixtures.ts +41 -0
- package/src/core/host/__tests__/notebook-client.test.ts +496 -0
- package/src/core/host/connection.ts +81 -0
- package/src/core/host/events.ts +68 -0
- package/src/core/host/notebook-client.ts +316 -0
- package/src/core/host/resources/attachments.ts +19 -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/network/connection.ts +4 -2
- package/src/core/run-app.tsx +2 -2
- package/src/core/runtime/__tests__/runtime.test.ts +5 -12
- package/src/core/runtime/runtime.ts +5 -5
- package/src/core/static/files.ts +2 -0
- package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -17
- package/src/core/websocket/__tests__/{useWebSocket.test.ts → createConnectionTransport.test.ts} +1 -1
- package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +149 -0
- package/src/core/websocket/__tests__/{useMarimoKernelConnection.hook.test.tsx → useNotebookSession.hook.test.tsx} +168 -253
- package/src/core/websocket/connection-state.ts +70 -0
- package/src/core/websocket/state.ts +7 -0
- package/src/core/websocket/transports/__tests__/sse.test.ts +18 -4
- package/src/core/websocket/transports/create.ts +31 -0
- package/src/core/websocket/transports/sse.ts +14 -2
- package/src/core/websocket/transports/transport.ts +12 -23
- package/src/core/websocket/types.ts +9 -39
- package/src/core/websocket/useNotebookClient.ts +78 -0
- package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +53 -233
- package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
- package/src/hooks/useAsyncData.ts +3 -3
- package/src/hooks/useDataSourceDiscovery.ts +6 -4
- package/src/hooks/useElapsedTime.ts +3 -3
- package/src/hooks/useInputHistory.ts +4 -7
- package/src/hooks/useResizeObserver.ts +4 -7
- package/src/mount.tsx +3 -3
- package/src/plugins/core/registerReactComponent.tsx +1 -1
- package/src/plugins/impl/DropdownPlugin.tsx +2 -6
- package/src/plugins/impl/FormPlugin.tsx +1 -1
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +84 -0
- 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/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/mpl-interactive/MplInteractivePlugin.tsx +5 -3
- package/src/plugins/impl/panel/PanelPlugin.tsx +15 -16
- 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 +3 -6
- package/src/stories/__tests__/editor-story.test.tsx +33 -0
- package/src/stories/editor.stories.tsx +24 -17
- package/src/utils/__tests__/arrays.test.ts +35 -0
- 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/numbers.ts +28 -17
- package/src/utils/objects.ts +3 -7
- package/src/utils/once.ts +19 -19
- package/dist/any-language-editor-DZ_YTVus.js +0 -76
- 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/websocket/useWebSocket.tsx +0 -88
|
@@ -11,10 +11,10 @@ import {
|
|
|
11
11
|
import { Provider } from "jotai";
|
|
12
12
|
import type React from "react";
|
|
13
13
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
14
|
-
import {
|
|
14
|
+
import { PAIR_CONFIG } from "@/__tests__/fixtures/pair-config";
|
|
15
15
|
import { Dialog } from "@/components/ui/dialog";
|
|
16
16
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
17
|
-
import {
|
|
17
|
+
import { pairConfigAtom } from "@/core/config/pair";
|
|
18
18
|
import {
|
|
19
19
|
type StableSessionId,
|
|
20
20
|
stableSessionIdAtom,
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
} from "@/core/runtime/config";
|
|
27
27
|
import { store } from "@/core/state/jotai";
|
|
28
28
|
import { PairWithAgentModal } from "../pair-with-agent-modal";
|
|
29
|
+
import { Deferred } from "@/utils/Deferred";
|
|
29
30
|
import { copyToClipboard } from "@/utils/copy";
|
|
30
31
|
|
|
31
32
|
vi.mock("@/utils/copy", () => ({
|
|
@@ -43,11 +44,24 @@ function wrapper({ children }: { children: React.ReactNode }) {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
const STABLE_ID = "sess-stable-1" as StableSessionId;
|
|
47
|
+
const NOTEBOOK_ID = "nb-one";
|
|
48
|
+
function responseFor(url: string | URL | Request, token: string | null = null) {
|
|
49
|
+
return Promise.resolve(
|
|
50
|
+
new Response(
|
|
51
|
+
JSON.stringify(
|
|
52
|
+
String(url).endsWith("/api/sessions")
|
|
53
|
+
? { one: { session_id: STABLE_ID, notebook_id: NOTEBOOK_ID } }
|
|
54
|
+
: { token },
|
|
55
|
+
),
|
|
56
|
+
{ headers: { "Content-Type": "application/json" } },
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
46
60
|
|
|
47
61
|
describe("PairWithAgentModal", () => {
|
|
48
62
|
beforeEach(() => {
|
|
49
63
|
vi.clearAllMocks();
|
|
50
|
-
store.set(
|
|
64
|
+
store.set(pairConfigAtom, PAIR_CONFIG);
|
|
51
65
|
store.set(stableSessionIdAtom, STABLE_ID);
|
|
52
66
|
store.set(filenameAtom, "/project/my notebook.py");
|
|
53
67
|
store.set(runtimeConfigAtom, {
|
|
@@ -57,19 +71,19 @@ describe("PairWithAgentModal", () => {
|
|
|
57
71
|
window.history.replaceState({}, "", "/?file=notebook.py");
|
|
58
72
|
vi.stubGlobal(
|
|
59
73
|
"fetch",
|
|
60
|
-
vi.fn().
|
|
74
|
+
vi.fn().mockImplementation((url) => responseFor(url)),
|
|
61
75
|
);
|
|
62
76
|
});
|
|
63
77
|
|
|
64
78
|
afterEach(() => {
|
|
65
|
-
store.set(
|
|
79
|
+
store.set(pairConfigAtom, undefined);
|
|
66
80
|
store.set(stableSessionIdAtom, null);
|
|
67
81
|
store.set(filenameAtom, null);
|
|
68
82
|
vi.unstubAllGlobals();
|
|
69
83
|
});
|
|
70
84
|
|
|
71
85
|
it.each(["Claude", "Codex", "OpenCode", "Prompt"])(
|
|
72
|
-
"
|
|
86
|
+
"targets the verified host Notebook in %s",
|
|
73
87
|
async (tab) => {
|
|
74
88
|
await act(async () => {
|
|
75
89
|
render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
|
|
@@ -81,10 +95,13 @@ describe("PairWithAgentModal", () => {
|
|
|
81
95
|
|
|
82
96
|
const panel = screen.getByRole("tabpanel");
|
|
83
97
|
expect(within(panel).queryAllByText(/skill/i)).toEqual([]);
|
|
84
|
-
expect(panel).toHaveTextContent(
|
|
98
|
+
expect(panel).toHaveTextContent(NOTEBOOK_ID);
|
|
85
99
|
expect(panel).not.toHaveTextContent("--file");
|
|
86
100
|
expect(panel).not.toHaveTextContent("File:");
|
|
87
101
|
expect(panel).not.toHaveTextContent("notebook.py");
|
|
102
|
+
expect(
|
|
103
|
+
panel.textContent?.includes("Agent connections require OpenCode 2.x"),
|
|
104
|
+
).toBe(tab === "OpenCode");
|
|
88
105
|
expect(panel).toHaveTextContent(
|
|
89
106
|
tab === "Prompt" ? "1. Copy this prompt" : "1. Run in your terminal",
|
|
90
107
|
);
|
|
@@ -95,13 +112,15 @@ describe("PairWithAgentModal", () => {
|
|
|
95
112
|
);
|
|
96
113
|
await waitFor(() => expect(copyToClipboard).toHaveBeenCalledOnce());
|
|
97
114
|
const copied = vi.mocked(copyToClipboard).mock.calls[0][0];
|
|
98
|
-
expect(copied).toContain(
|
|
115
|
+
expect(copied).toContain(NOTEBOOK_ID);
|
|
99
116
|
expect(copied).not.toContain("notebook.py");
|
|
117
|
+
expect(copied).not.toContain(STABLE_ID);
|
|
118
|
+
expect(panel).not.toHaveTextContent(STABLE_ID);
|
|
100
119
|
},
|
|
101
120
|
);
|
|
102
121
|
|
|
103
122
|
it.each(["Claude", "Prompt"])(
|
|
104
|
-
"
|
|
123
|
+
"waits for kernel-ready before copying the %s target",
|
|
105
124
|
async (tab) => {
|
|
106
125
|
store.set(stableSessionIdAtom, null);
|
|
107
126
|
await act(async () => {
|
|
@@ -113,17 +132,21 @@ describe("PairWithAgentModal", () => {
|
|
|
113
132
|
});
|
|
114
133
|
|
|
115
134
|
const panel = screen.getByRole("tabpanel");
|
|
116
|
-
expect(panel).not.toHaveTextContent("--
|
|
117
|
-
expect(panel).not.toHaveTextContent("
|
|
135
|
+
expect(panel).not.toHaveTextContent("--notebook");
|
|
136
|
+
expect(panel).not.toHaveTextContent("Notebook:");
|
|
118
137
|
expect(panel).toHaveTextContent("http://localhost:8000");
|
|
138
|
+
expect(
|
|
139
|
+
within(panel).getByRole("button", {
|
|
140
|
+
name: tab === "Prompt" ? "Copy prompt" : "Copy command",
|
|
141
|
+
}),
|
|
142
|
+
).toBeDisabled();
|
|
119
143
|
},
|
|
120
144
|
);
|
|
121
145
|
|
|
122
146
|
it.each(["Claude", "Codex", "OpenCode", "Prompt"])(
|
|
123
|
-
"
|
|
147
|
+
"uses the editor CLI in %s when host is disabled",
|
|
124
148
|
async (tab) => {
|
|
125
|
-
store.set(
|
|
126
|
-
window.history.replaceState({}, "", "/");
|
|
149
|
+
store.set(pairConfigAtom, { ...PAIR_CONFIG, hostEnabled: false });
|
|
127
150
|
await act(async () => {
|
|
128
151
|
render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
|
|
129
152
|
});
|
|
@@ -131,27 +154,33 @@ describe("PairWithAgentModal", () => {
|
|
|
131
154
|
button: 0,
|
|
132
155
|
ctrlKey: false,
|
|
133
156
|
});
|
|
134
|
-
|
|
135
157
|
const panel = screen.getByRole("tabpanel");
|
|
136
|
-
expect(
|
|
137
|
-
|
|
138
|
-
).
|
|
139
|
-
expect(panel).not.toHaveTextContent(
|
|
140
|
-
expect(panel).not.toHaveTextContent("--file");
|
|
158
|
+
expect(panel).toHaveTextContent(STABLE_ID);
|
|
159
|
+
expect(panel).not.toHaveTextContent(NOTEBOOK_ID);
|
|
160
|
+
expect(panel).not.toHaveTextContent("skill");
|
|
161
|
+
expect(panel).not.toHaveTextContent("Agent connections require");
|
|
141
162
|
expect(panel).not.toHaveTextContent("MARIMO_PAIR_NEXT");
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
163
|
+
fireEvent.click(
|
|
164
|
+
within(panel).getByRole("button", {
|
|
165
|
+
name: tab === "Prompt" ? "Copy prompt" : "Copy command",
|
|
166
|
+
}),
|
|
145
167
|
);
|
|
168
|
+
await waitFor(() => expect(copyToClipboard).toHaveBeenCalledOnce());
|
|
169
|
+
expect(vi.mocked(copyToClipboard).mock.calls[0][0]).toContain(STABLE_ID);
|
|
170
|
+
expect(
|
|
171
|
+
vi
|
|
172
|
+
.mocked(fetch)
|
|
173
|
+
.mock.calls.some(([url]) => String(url).endsWith("/api/sessions")),
|
|
174
|
+
).toBe(false);
|
|
146
175
|
},
|
|
147
176
|
);
|
|
148
177
|
|
|
149
178
|
it.each([true, false])(
|
|
150
|
-
"masks the raw token but copies it intact (
|
|
151
|
-
async (
|
|
152
|
-
store.set(
|
|
153
|
-
vi.mocked(fetch).
|
|
154
|
-
|
|
179
|
+
"masks the raw token but copies it intact (host mode=%s)",
|
|
180
|
+
async (hostEnabled) => {
|
|
181
|
+
store.set(pairConfigAtom, { ...PAIR_CONFIG, hostEnabled });
|
|
182
|
+
vi.mocked(fetch).mockImplementation((url) =>
|
|
183
|
+
responseFor(url, "secret-token"),
|
|
155
184
|
);
|
|
156
185
|
await act(async () => {
|
|
157
186
|
render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
|
|
@@ -169,7 +198,7 @@ describe("PairWithAgentModal", () => {
|
|
|
169
198
|
);
|
|
170
199
|
await waitFor(() => expect(copyToClipboard).toHaveBeenCalledOnce());
|
|
171
200
|
expect(vi.mocked(copyToClipboard).mock.calls[0][0]).toContain(
|
|
172
|
-
|
|
201
|
+
"export MARIMO_TOKEN=secret-token",
|
|
173
202
|
);
|
|
174
203
|
expect(vi.mocked(copyToClipboard).mock.calls[0][0]).not.toContain(
|
|
175
204
|
"********oken",
|
|
@@ -177,9 +206,132 @@ describe("PairWithAgentModal", () => {
|
|
|
177
206
|
},
|
|
178
207
|
);
|
|
179
208
|
|
|
180
|
-
it("
|
|
181
|
-
|
|
182
|
-
|
|
209
|
+
it("uses the CLI when an older server omits Pair configuration", async () => {
|
|
210
|
+
store.set(pairConfigAtom, undefined);
|
|
211
|
+
await act(async () => {
|
|
212
|
+
render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
|
|
213
|
+
});
|
|
214
|
+
const panel = screen.getByRole("tabpanel");
|
|
215
|
+
expect(panel).toHaveTextContent(`--session ${STABLE_ID}`);
|
|
216
|
+
expect(panel).not.toHaveTextContent("skill");
|
|
217
|
+
expect(screen.getByRole("button", { name: "Copy command" })).toBeEnabled();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("ignores a previous Session's late Notebook lookup", async () => {
|
|
221
|
+
const previous = new Deferred<Response>();
|
|
222
|
+
const nextId = "sess-next" as StableSessionId;
|
|
223
|
+
let lookups = 0;
|
|
224
|
+
vi.mocked(fetch).mockImplementation((url) => {
|
|
225
|
+
if (!String(url).endsWith("/api/sessions")) {
|
|
226
|
+
return responseFor(url);
|
|
227
|
+
}
|
|
228
|
+
if (lookups++ === 0) {
|
|
229
|
+
return previous.promise;
|
|
230
|
+
}
|
|
231
|
+
return Promise.resolve(
|
|
232
|
+
new Response(
|
|
233
|
+
JSON.stringify({
|
|
234
|
+
next: { session_id: nextId, notebook_id: "nb-next" },
|
|
235
|
+
}),
|
|
236
|
+
{ headers: { "Content-Type": "application/json" } },
|
|
237
|
+
),
|
|
238
|
+
);
|
|
239
|
+
});
|
|
240
|
+
await act(async () => {
|
|
241
|
+
render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
|
|
242
|
+
});
|
|
243
|
+
await act(async () => store.set(stableSessionIdAtom, nextId));
|
|
244
|
+
expect(screen.getByRole("tabpanel")).toHaveTextContent("nb-next");
|
|
245
|
+
await act(async () =>
|
|
246
|
+
previous.resolve(
|
|
247
|
+
new Response(
|
|
248
|
+
JSON.stringify({
|
|
249
|
+
previous: { session_id: STABLE_ID, notebook_id: NOTEBOOK_ID },
|
|
250
|
+
}),
|
|
251
|
+
{ headers: { "Content-Type": "application/json" } },
|
|
252
|
+
),
|
|
253
|
+
),
|
|
254
|
+
);
|
|
255
|
+
expect(screen.getByRole("tabpanel")).toHaveTextContent("nb-next");
|
|
256
|
+
expect(screen.getByRole("tabpanel")).not.toHaveTextContent(NOTEBOOK_ID);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("disables target copying while a new Session's Notebook is unverified", async () => {
|
|
260
|
+
await act(async () => {
|
|
261
|
+
render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
|
|
262
|
+
});
|
|
263
|
+
expect(screen.getByRole("tabpanel")).toHaveTextContent(NOTEBOOK_ID);
|
|
264
|
+
const pending = new Deferred<Response>();
|
|
265
|
+
vi.mocked(fetch).mockImplementation((url) =>
|
|
266
|
+
String(url).endsWith("/api/sessions")
|
|
267
|
+
? pending.promise
|
|
268
|
+
: responseFor(url),
|
|
269
|
+
);
|
|
270
|
+
const nextId = "sess-stable-2" as StableSessionId;
|
|
271
|
+
act(() => store.set(stableSessionIdAtom, nextId));
|
|
272
|
+
expect(screen.getByRole("button", { name: "Copy command" })).toBeDisabled();
|
|
273
|
+
expect(screen.getByRole("tabpanel")).not.toHaveTextContent(NOTEBOOK_ID);
|
|
274
|
+
fireEvent.click(screen.getByRole("button", { name: "Copy command" }));
|
|
275
|
+
expect(copyToClipboard).not.toHaveBeenCalled();
|
|
276
|
+
await act(async () =>
|
|
277
|
+
pending.resolve(
|
|
278
|
+
new Response(
|
|
279
|
+
JSON.stringify({
|
|
280
|
+
next: { session_id: nextId, notebook_id: "nb-two" },
|
|
281
|
+
}),
|
|
282
|
+
{ headers: { "Content-Type": "application/json" } },
|
|
283
|
+
),
|
|
284
|
+
),
|
|
285
|
+
);
|
|
286
|
+
expect(screen.getByRole("button", { name: "Copy command" })).toBeEnabled();
|
|
287
|
+
fireEvent.click(screen.getByRole("button", { name: "Copy command" }));
|
|
288
|
+
await waitFor(() => expect(copyToClipboard).toHaveBeenCalledOnce());
|
|
289
|
+
expect(vi.mocked(copyToClipboard).mock.calls[0][0]).toContain("nb-two");
|
|
290
|
+
expect(vi.mocked(copyToClipboard).mock.calls[0][0]).not.toContain(nextId);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("uses the Session after verifying that a notebook is unsaved", async () => {
|
|
294
|
+
vi.mocked(fetch).mockImplementation((url) =>
|
|
295
|
+
String(url).endsWith("/api/sessions")
|
|
296
|
+
? Promise.resolve(
|
|
297
|
+
new Response(
|
|
298
|
+
JSON.stringify({
|
|
299
|
+
one: { session_id: STABLE_ID, notebook_id: null },
|
|
300
|
+
}),
|
|
301
|
+
{ headers: { "Content-Type": "application/json" } },
|
|
302
|
+
),
|
|
303
|
+
)
|
|
304
|
+
: responseFor(url),
|
|
305
|
+
);
|
|
306
|
+
await act(async () => {
|
|
307
|
+
render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
|
|
308
|
+
});
|
|
309
|
+
expect(screen.queryByRole("status")).not.toBeInTheDocument();
|
|
310
|
+
expect(screen.getByRole("button", { name: "Copy command" })).toBeEnabled();
|
|
311
|
+
expect(screen.getByRole("tabpanel")).toHaveTextContent(
|
|
312
|
+
`--session ${STABLE_ID}`,
|
|
313
|
+
);
|
|
314
|
+
expect(screen.getByRole("tabpanel")).not.toHaveTextContent("--notebook");
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("keeps copying disabled when Notebook verification fails", async () => {
|
|
318
|
+
vi.mocked(fetch).mockImplementation((url) =>
|
|
319
|
+
String(url).endsWith("/api/sessions")
|
|
320
|
+
? Promise.reject(new Error("offline"))
|
|
321
|
+
: responseFor(url),
|
|
322
|
+
);
|
|
323
|
+
await act(async () => {
|
|
324
|
+
render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
|
|
325
|
+
});
|
|
326
|
+
expect(screen.getByRole("status")).toHaveTextContent(
|
|
327
|
+
"Could not verify this notebook",
|
|
328
|
+
);
|
|
329
|
+
expect(screen.getByRole("button", { name: "Copy command" })).toBeDisabled();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("copies a host mode terminal command without the token and lets the user copy the token separately", async () => {
|
|
333
|
+
vi.mocked(fetch).mockImplementation((url) =>
|
|
334
|
+
responseFor(url, "secret-token"),
|
|
183
335
|
);
|
|
184
336
|
await act(async () => {
|
|
185
337
|
render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
|
|
@@ -193,9 +345,9 @@ describe("PairWithAgentModal", () => {
|
|
|
193
345
|
);
|
|
194
346
|
await waitFor(() =>
|
|
195
347
|
expect(copyToClipboard).toHaveBeenCalledWith(
|
|
196
|
-
String.raw`claude "$(
|
|
348
|
+
String.raw`claude "$(uvx marimo@latest pair prompt \
|
|
197
349
|
--url http://localhost:8000/ \
|
|
198
|
-
--
|
|
350
|
+
--notebook ${NOTEBOOK_ID} \
|
|
199
351
|
--with-token)"`,
|
|
200
352
|
),
|
|
201
353
|
);
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { assertNever } from "@/utils/assertNever";
|
|
4
|
-
import { KnownQueryParams } from "@/core/constants";
|
|
5
4
|
import { shellQuote } from "@/utils/shell";
|
|
6
|
-
import type {
|
|
5
|
+
import type { PairConfig } from "@/core/config/pair";
|
|
7
6
|
|
|
8
7
|
export type AgentTab = "claude" | "codex" | "opencode" | "prompt";
|
|
9
8
|
|
|
@@ -18,30 +17,18 @@ export const AGENT_LABELS: Record<AgentTab, string> = {
|
|
|
18
17
|
prompt: "Prompt",
|
|
19
18
|
};
|
|
20
19
|
|
|
21
|
-
export const SKILL_INSTALL = "npx skills add marimo-team/marimo-pair";
|
|
22
|
-
|
|
23
20
|
/** How to invoke marimo: from the local checkout in dev, else via uvx. */
|
|
24
21
|
export function getMarimoCommand(): string {
|
|
25
22
|
return import.meta.env.DEV ? "uv run marimo" : "uvx marimo@latest";
|
|
26
23
|
}
|
|
27
24
|
|
|
28
|
-
/** Return the server file key from a page URL, preserving its decoded value. */
|
|
29
|
-
export function getFileFromURL(href: string): string | undefined {
|
|
30
|
-
const file = new URL(href).searchParams.get(KnownQueryParams.filePath);
|
|
31
|
-
return file === null || file === "" ? undefined : file;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function getFileFlag(file: string | undefined): string {
|
|
35
|
-
return file ? ` --file ${shellQuote(file)}` : "";
|
|
36
|
-
}
|
|
37
|
-
|
|
38
25
|
/** Identifies the specific running notebook to pair on. */
|
|
39
26
|
export interface ConnectionInfo {
|
|
40
27
|
url: string;
|
|
41
|
-
/**
|
|
42
|
-
file?: string;
|
|
43
|
-
/** The stable session ID, when known. */
|
|
28
|
+
/** Stable Session ID for ordinary editor execution. */
|
|
44
29
|
session?: string;
|
|
30
|
+
/** The host notebook ID, when known. */
|
|
31
|
+
notebook?: string;
|
|
45
32
|
}
|
|
46
33
|
|
|
47
34
|
/**
|
|
@@ -50,21 +37,23 @@ export interface ConnectionInfo {
|
|
|
50
37
|
*/
|
|
51
38
|
export function getTerminalCommand(
|
|
52
39
|
agent: Exclude<AgentTab, "prompt">,
|
|
53
|
-
{ url, session }: ConnectionInfo,
|
|
40
|
+
{ url, notebook, session }: ConnectionInfo,
|
|
54
41
|
withToken: boolean,
|
|
55
|
-
|
|
42
|
+
config?: PairConfig,
|
|
56
43
|
): string {
|
|
57
|
-
const command =
|
|
58
|
-
const prefix = preview ? "MARIMO_PAIR_NEXT=1 " : "";
|
|
44
|
+
const command = config?.command ?? getMarimoCommand();
|
|
59
45
|
const base = [
|
|
60
|
-
`${
|
|
46
|
+
`${command} pair prompt`,
|
|
61
47
|
`--url ${shellQuote(url)}`,
|
|
62
|
-
|
|
48
|
+
notebook
|
|
49
|
+
? `--notebook ${shellQuote(notebook)}`
|
|
50
|
+
: session
|
|
51
|
+
? `--session ${shellQuote(session)}`
|
|
52
|
+
: "",
|
|
63
53
|
withToken ? "--with-token" : "",
|
|
64
|
-
preview ? "" : `--${agent}`,
|
|
65
54
|
]
|
|
66
55
|
.filter(Boolean)
|
|
67
|
-
.join(
|
|
56
|
+
.join(" \\\n ");
|
|
68
57
|
switch (agent) {
|
|
69
58
|
case "claude":
|
|
70
59
|
return `claude "$(${base})"`;
|
|
@@ -83,37 +72,39 @@ export function getTerminalCommand(
|
|
|
83
72
|
* an agent behaves the same as the terminal commands.
|
|
84
73
|
*/
|
|
85
74
|
export function getRawPrompt(
|
|
86
|
-
{ url,
|
|
75
|
+
{ url, notebook, session }: ConnectionInfo,
|
|
87
76
|
token: string | null,
|
|
88
|
-
|
|
77
|
+
config?: PairConfig,
|
|
89
78
|
): string {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
79
|
+
const command = config?.command ?? getMarimoCommand();
|
|
80
|
+
const templates = config?.templates;
|
|
81
|
+
const authentication = token
|
|
82
|
+
? formatPrompt(
|
|
83
|
+
templates?.token ??
|
|
84
|
+
"\n\nFor authenticated notebook discovery, set `export MARIMO_TOKEN={token}` in the shell that runs marimo.",
|
|
85
|
+
{ token: shellQuote(token) },
|
|
86
|
+
)
|
|
87
|
+
: "";
|
|
88
|
+
return formatPrompt(
|
|
89
|
+
templates?.prompt ??
|
|
90
|
+
"Pair with me on this running marimo notebook.\n\nURL: {url}\n{notebook}{session}\nRun `{command} pair --help` first.\nUse `{command}` for all marimo commands.{authentication}",
|
|
91
|
+
{
|
|
93
92
|
command,
|
|
94
93
|
url,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
notebook: notebook
|
|
95
|
+
? formatPrompt(templates?.notebook ?? "Notebook: {notebook}\n", {
|
|
96
|
+
notebook,
|
|
97
|
+
})
|
|
98
98
|
: "",
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return [
|
|
109
|
-
"Use the /marimo-pair skill to pair-program on a running marimo notebook.",
|
|
110
|
-
"",
|
|
111
|
-
`Connect to the notebook at: ${url}${fileHint}`,
|
|
112
|
-
"",
|
|
113
|
-
`Use \`${executeCmd}\` from the marimo-pair skill to execute code in the notebook.${tokenHint}`,
|
|
114
|
-
"",
|
|
115
|
-
"Once you are connected, send a fun toast (mo.status.toast(...)) to the user inside marimo letting them know you're ready to pair.",
|
|
116
|
-
].join("\n");
|
|
99
|
+
session:
|
|
100
|
+
session && !notebook
|
|
101
|
+
? formatPrompt(templates?.session ?? "Session: {session}\n", {
|
|
102
|
+
session,
|
|
103
|
+
})
|
|
104
|
+
: "",
|
|
105
|
+
authentication,
|
|
106
|
+
},
|
|
107
|
+
);
|
|
117
108
|
}
|
|
118
109
|
|
|
119
110
|
function formatPrompt(
|