@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
|
@@ -21,14 +21,9 @@ import {
|
|
|
21
21
|
vi,
|
|
22
22
|
} from "vitest";
|
|
23
23
|
|
|
24
|
-
vi.mock("
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return {
|
|
28
|
-
...actual,
|
|
29
|
-
useConnectionTransport: vi.fn(),
|
|
30
|
-
};
|
|
31
|
-
});
|
|
24
|
+
vi.mock("../transports/create", () => ({
|
|
25
|
+
createConnectionTransport: vi.fn(),
|
|
26
|
+
}));
|
|
32
27
|
|
|
33
28
|
vi.mock("@/core/runtime/config", async () => {
|
|
34
29
|
const actual = await vi.importActual<typeof import("@/core/runtime/config")>(
|
|
@@ -41,26 +36,27 @@ vi.mock("@/core/runtime/config", async () => {
|
|
|
41
36
|
});
|
|
42
37
|
|
|
43
38
|
import { MockNotebook } from "@/__mocks__/notebook";
|
|
39
|
+
import { Logger } from "@/utils/Logger";
|
|
40
|
+
import { Deferred } from "@/utils/Deferred";
|
|
44
41
|
import { cellId } from "@/__tests__/branded";
|
|
45
42
|
import { alertAtom, getPackageAlert } from "@/core/alerts/state";
|
|
46
|
-
import {
|
|
47
|
-
type Attachment,
|
|
48
|
-
attachmentsAtom,
|
|
49
|
-
agentAttachmentsAtom,
|
|
50
|
-
} from "@/core/attachments/state";
|
|
51
43
|
import { notebookAtom } from "@/core/cells/cells";
|
|
52
44
|
import { AppConfigSchema } from "@/core/config/config-schema";
|
|
53
45
|
import { ConnectionNotice } from "@/components/editor/alerts/connection-notice";
|
|
54
46
|
import { kernelStartupErrorAtom } from "@/core/errors/state";
|
|
47
|
+
import type { RunEvent } from "@marimo-team/client/protocol";
|
|
55
48
|
import type { NotificationPayload } from "@/core/kernel/messages";
|
|
56
49
|
import { useRuntimeManager } from "@/core/runtime/config";
|
|
57
50
|
import { initialRunCompletedAtom } from "../../kernel/state";
|
|
58
51
|
import { connectionAtom, startupProgressAtom } from "../../network/connection";
|
|
59
|
-
import
|
|
52
|
+
import { generateSessionId } from "../../kernel/session";
|
|
53
|
+
import { RuntimeManager } from "../../runtime/runtime";
|
|
60
54
|
import { WebSocketClosedReason, ConnectionState } from "../types";
|
|
61
55
|
import type { IConnectionTransport } from "../transports/transport";
|
|
62
|
-
import {
|
|
63
|
-
import {
|
|
56
|
+
import { useNotebookSession } from "../useNotebookSession";
|
|
57
|
+
import { createConnectionTransport } from "../transports/create";
|
|
58
|
+
|
|
59
|
+
const SESSION_ID = generateSessionId();
|
|
64
60
|
|
|
65
61
|
interface MockTransport {
|
|
66
62
|
readyState: 0 | 1 | 2 | 3;
|
|
@@ -84,16 +80,47 @@ function makeTransport(
|
|
|
84
80
|
};
|
|
85
81
|
}
|
|
86
82
|
|
|
83
|
+
function getTransportHandlers() {
|
|
84
|
+
const result = vi.mocked(createConnectionTransport).mock.results.at(-1);
|
|
85
|
+
if (!result || result.type !== "return") {
|
|
86
|
+
throw new Error("No connection transport was created");
|
|
87
|
+
}
|
|
88
|
+
const transport = result.value;
|
|
89
|
+
function emit(
|
|
90
|
+
event: "open" | "close" | "error" | "message",
|
|
91
|
+
message: Event | CloseEvent | MessageEvent,
|
|
92
|
+
) {
|
|
93
|
+
const callback = vi
|
|
94
|
+
.mocked(transport.addEventListener)
|
|
95
|
+
.mock.calls.find(([name]) => name === event)?.[1];
|
|
96
|
+
callback?.(message);
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
onError: (event: Event) => emit("error", event),
|
|
100
|
+
onOpen: (event: Event) => emit("open", event),
|
|
101
|
+
onClose: (event: CloseEvent) => emit("close", event),
|
|
102
|
+
onMessage: (event: MessageEvent) => emit("message", event),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
87
106
|
function makeRuntimeManager(
|
|
88
107
|
reconcileFromHealth = vi.fn<() => Promise<boolean>>().mockResolvedValue(true),
|
|
89
108
|
) {
|
|
90
|
-
|
|
109
|
+
const runtime = new RuntimeManager(
|
|
110
|
+
{ url: "http://localhost", lazy: true },
|
|
111
|
+
true,
|
|
112
|
+
);
|
|
113
|
+
vi.spyOn(runtime, "reconcileFromHealth").mockImplementation(
|
|
91
114
|
reconcileFromHealth,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
115
|
+
);
|
|
116
|
+
vi.spyOn(runtime, "probeHealth").mockResolvedValue(true);
|
|
117
|
+
vi.spyOn(runtime, "getWsURL").mockReturnValue(new URL("ws://localhost/ws"));
|
|
118
|
+
// Keep the initial connection pending; these tests exercise session retries.
|
|
119
|
+
vi.spyOn(runtime, "waitForHealthy").mockReturnValue(
|
|
120
|
+
new Deferred<void>().promise,
|
|
121
|
+
);
|
|
122
|
+
vi.spyOn(runtime, "isSameOrigin", "get").mockReturnValue(false);
|
|
123
|
+
return runtime;
|
|
97
124
|
}
|
|
98
125
|
|
|
99
126
|
function renderConnectionHook(store: ReturnType<typeof createStore>) {
|
|
@@ -104,16 +131,16 @@ function renderConnectionHook(store: ReturnType<typeof createStore>) {
|
|
|
104
131
|
);
|
|
105
132
|
return renderHook(
|
|
106
133
|
() =>
|
|
107
|
-
|
|
108
|
-
sessionId:
|
|
134
|
+
useNotebookSession({
|
|
135
|
+
sessionId: SESSION_ID,
|
|
109
136
|
autoInstantiate: false,
|
|
110
|
-
setCells: ()
|
|
137
|
+
setCells: vi.fn(),
|
|
111
138
|
}),
|
|
112
139
|
{ wrapper },
|
|
113
140
|
);
|
|
114
141
|
}
|
|
115
142
|
|
|
116
|
-
describe("
|
|
143
|
+
describe("useNotebookSession.reconnect()", () => {
|
|
117
144
|
let transport: MockTransport;
|
|
118
145
|
let isHealthy: Mock<() => Promise<boolean>>;
|
|
119
146
|
let store: ReturnType<typeof createStore>;
|
|
@@ -127,12 +154,8 @@ describe("useMarimoKernelConnection.reconnect()", () => {
|
|
|
127
154
|
code: WebSocketClosedReason.KERNEL_DISCONNECTED,
|
|
128
155
|
reason: "kernel not found",
|
|
129
156
|
});
|
|
130
|
-
vi.mocked(
|
|
131
|
-
vi.mocked(useRuntimeManager).mockReturnValue(
|
|
132
|
-
makeRuntimeManager(isHealthy) as unknown as ReturnType<
|
|
133
|
-
typeof useRuntimeManager
|
|
134
|
-
>,
|
|
135
|
-
);
|
|
157
|
+
vi.mocked(createConnectionTransport).mockReturnValue(transport);
|
|
158
|
+
vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager(isHealthy));
|
|
136
159
|
});
|
|
137
160
|
|
|
138
161
|
function renderUseHook() {
|
|
@@ -188,166 +211,117 @@ describe("useMarimoKernelConnection.reconnect()", () => {
|
|
|
188
211
|
});
|
|
189
212
|
});
|
|
190
213
|
|
|
191
|
-
describe("
|
|
192
|
-
|
|
193
|
-
const store = createStore();
|
|
194
|
-
vi.mocked(useConnectionTransport).mockReturnValue(
|
|
195
|
-
makeTransport(WebSocket.OPEN),
|
|
196
|
-
);
|
|
197
|
-
vi.mocked(useRuntimeManager).mockReturnValue(
|
|
198
|
-
makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
|
|
199
|
-
);
|
|
200
|
-
renderConnectionHook(store);
|
|
201
|
-
const options = vi.mocked(useConnectionTransport).mock.calls.at(-1)![0];
|
|
202
|
-
const agent: Attachment = {
|
|
203
|
-
id: "a1",
|
|
204
|
-
kind: "agent",
|
|
205
|
-
name: "Pi",
|
|
206
|
-
since: "2026-10-14T00:00:00Z",
|
|
207
|
-
notebook_id: "nb-test",
|
|
208
|
-
identity: "pi-thread",
|
|
209
|
-
};
|
|
210
|
-
const client: Attachment = {
|
|
211
|
-
id: "c1",
|
|
212
|
-
kind: "client",
|
|
213
|
-
name: null,
|
|
214
|
-
since: "2026-10-14T00:00:01Z",
|
|
215
|
-
notebook_id: "nb-test",
|
|
216
|
-
};
|
|
217
|
-
for (const attachments of [[agent, client], [client], []]) {
|
|
218
|
-
act(() =>
|
|
219
|
-
options.onMessage(
|
|
220
|
-
new MessageEvent("message", {
|
|
221
|
-
data: JSON.stringify({
|
|
222
|
-
op: "attachments",
|
|
223
|
-
data: { op: "attachments", attachments },
|
|
224
|
-
}),
|
|
225
|
-
}),
|
|
226
|
-
),
|
|
227
|
-
);
|
|
228
|
-
expect(store.get(attachmentsAtom)).toEqual(attachments);
|
|
229
|
-
}
|
|
230
|
-
});
|
|
214
|
+
describe("useNotebookSession messages", () => {
|
|
215
|
+
let store: ReturnType<typeof createStore>;
|
|
231
216
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
vi.mocked(
|
|
235
|
-
vi.mocked(
|
|
217
|
+
beforeEach(() => {
|
|
218
|
+
store = createStore();
|
|
219
|
+
vi.mocked(createConnectionTransport).mockClear();
|
|
220
|
+
vi.mocked(createConnectionTransport).mockReturnValue(
|
|
236
221
|
makeTransport(WebSocket.OPEN),
|
|
237
222
|
);
|
|
238
|
-
vi.mocked(useRuntimeManager).mockReturnValue(
|
|
239
|
-
makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
|
|
240
|
-
);
|
|
223
|
+
vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
|
|
241
224
|
renderConnectionHook(store);
|
|
225
|
+
});
|
|
242
226
|
|
|
243
|
-
|
|
244
|
-
|
|
227
|
+
function receive(payload: unknown) {
|
|
228
|
+
const options = getTransportHandlers();
|
|
245
229
|
act(() => {
|
|
246
|
-
options
|
|
247
|
-
new MessageEvent("message", {
|
|
248
|
-
data: JSON.stringify({
|
|
249
|
-
op: "completed-run",
|
|
250
|
-
data: { op: "completed-run", run_id: null },
|
|
251
|
-
}),
|
|
252
|
-
}),
|
|
230
|
+
options.onMessage(
|
|
231
|
+
new MessageEvent("message", { data: JSON.stringify(payload) }),
|
|
253
232
|
);
|
|
254
233
|
});
|
|
255
|
-
expect(store.get(initialRunCompletedAtom)).toBe(true);
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
describe("attachment snapshots across browser connections", () => {
|
|
260
|
-
const agent: Attachment = {
|
|
261
|
-
id: "a1",
|
|
262
|
-
kind: "agent",
|
|
263
|
-
name: "Pi",
|
|
264
|
-
since: "2026-10-14T00:00:00Z",
|
|
265
|
-
notebook_id: "nb-test",
|
|
266
|
-
identity: "pi-thread",
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
function connectedSession() {
|
|
270
|
-
const store = createStore();
|
|
271
|
-
store.set(connectionAtom, { state: ConnectionState.OPEN });
|
|
272
|
-
store.set(attachmentsAtom, [agent]);
|
|
273
|
-
const transport = makeTransport(WebSocket.OPEN);
|
|
274
|
-
vi.mocked(useConnectionTransport).mockReturnValue(transport);
|
|
275
|
-
vi.mocked(useRuntimeManager).mockReturnValue(
|
|
276
|
-
makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
|
|
277
|
-
);
|
|
278
|
-
renderConnectionHook(store);
|
|
279
|
-
const options = vi.mocked(useConnectionTransport).mock.calls.at(-1)![0];
|
|
280
|
-
const receive = (data: NotificationPayload["data"]) =>
|
|
281
|
-
act(() => {
|
|
282
|
-
options.onMessage(
|
|
283
|
-
new MessageEvent("message", {
|
|
284
|
-
data: JSON.stringify({ op: data.op, data }),
|
|
285
|
-
}),
|
|
286
|
-
);
|
|
287
|
-
});
|
|
288
|
-
return { store, transport, options, receive };
|
|
289
234
|
}
|
|
290
235
|
|
|
291
|
-
it.each([
|
|
292
|
-
"
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const { store, transport, options, receive } = connectedSession();
|
|
318
|
-
act(() => options.onClose(new CloseEvent("close")));
|
|
319
|
-
expect(store.get(attachmentsAtom)).toEqual([]);
|
|
320
|
-
expect(store.get(connectionAtom).state).toBe(ConnectionState.CONNECTING);
|
|
321
|
-
expect(transport.reconnect).toHaveBeenCalledOnce();
|
|
322
|
-
|
|
323
|
-
await act(async () => options.onOpen(new Event("open")));
|
|
324
|
-
receive({ op: "reconnected" });
|
|
325
|
-
expect(store.get(connectionAtom).state).toBe(ConnectionState.OPEN);
|
|
326
|
-
expect(store.get(agentAttachmentsAtom)).toEqual([]);
|
|
327
|
-
|
|
328
|
-
const refreshed = { ...agent, id: "a2", name: "Codex" };
|
|
329
|
-
receive({ op: "attachments", attachments: [refreshed] });
|
|
330
|
-
expect(store.get(agentAttachmentsAtom)).toEqual([refreshed]);
|
|
331
|
-
receive({ op: "attachments", attachments: [] });
|
|
332
|
-
expect(store.get(agentAttachmentsAtom)).toEqual([]);
|
|
333
|
-
});
|
|
236
|
+
it.each(["{broken", "null"])(
|
|
237
|
+
"closes a malformed stream and ignores subsequent notifications: %s",
|
|
238
|
+
(data) => {
|
|
239
|
+
receive({ event: "marimo", data: { marimo: { op: "reconnected" } } });
|
|
240
|
+
act(() =>
|
|
241
|
+
getTransportHandlers().onMessage(new MessageEvent("message", { data })),
|
|
242
|
+
);
|
|
243
|
+
expect(store.get(connectionAtom)).toEqual({
|
|
244
|
+
state: ConnectionState.CLOSED,
|
|
245
|
+
code: "KERNEL_DISCONNECTED",
|
|
246
|
+
reason: "Failed to decode notebook message",
|
|
247
|
+
});
|
|
248
|
+
receive({
|
|
249
|
+
event: "marimo",
|
|
250
|
+
data: {
|
|
251
|
+
marimo: {
|
|
252
|
+
op: "completed-run",
|
|
253
|
+
run_id: "r1",
|
|
254
|
+
outcome: "succeeded",
|
|
255
|
+
error: null,
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
expect(store.get(initialRunCompletedAtom)).toBe(false);
|
|
260
|
+
},
|
|
261
|
+
);
|
|
334
262
|
|
|
335
|
-
it("
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
263
|
+
it.each(["session", "host"])(
|
|
264
|
+
"records completion of the initial run from the %s stream",
|
|
265
|
+
(stream) => {
|
|
266
|
+
const notification = {
|
|
267
|
+
op: "completed-run",
|
|
268
|
+
run_id: null,
|
|
269
|
+
outcome: "succeeded",
|
|
270
|
+
error: null,
|
|
271
|
+
} satisfies NotificationPayload["data"];
|
|
272
|
+
expect(store.get(initialRunCompletedAtom)).toBe(false);
|
|
273
|
+
receive(
|
|
274
|
+
stream === "host"
|
|
275
|
+
? { id: "42", event: "marimo", data: { marimo: notification } }
|
|
276
|
+
: { op: notification.op, data: notification },
|
|
277
|
+
);
|
|
278
|
+
expect(store.get(initialRunCompletedAtom)).toBe(true);
|
|
279
|
+
},
|
|
280
|
+
);
|
|
345
281
|
|
|
346
|
-
it("
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
282
|
+
it("ignores other host events before handling the next notification", () => {
|
|
283
|
+
const errors = vi.spyOn(Logger, "error").mockImplementation(vi.fn());
|
|
284
|
+
try {
|
|
285
|
+
receive({ id: "1", event: "ready", data: {} });
|
|
286
|
+
receive({
|
|
287
|
+
id: "2",
|
|
288
|
+
event: "run",
|
|
289
|
+
data: {
|
|
290
|
+
run: {
|
|
291
|
+
id: "r1",
|
|
292
|
+
notebook_id: "n1",
|
|
293
|
+
caused_by: null,
|
|
294
|
+
status: "completed",
|
|
295
|
+
outcome: "succeeded",
|
|
296
|
+
started_at: "2026-10-19T12:00:00Z",
|
|
297
|
+
completed_at: "2026-10-19T12:00:01Z",
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
} satisfies RunEvent);
|
|
301
|
+
receive({
|
|
302
|
+
id: "3",
|
|
303
|
+
event: "future-event",
|
|
304
|
+
data: { marimo: { op: "completed-run", run_id: "r1" } },
|
|
305
|
+
});
|
|
306
|
+
receive({ id: "4", event: "another-event", data: null });
|
|
307
|
+
expect(store.get(initialRunCompletedAtom)).toBe(false);
|
|
308
|
+
receive({
|
|
309
|
+
id: "5",
|
|
310
|
+
event: "marimo",
|
|
311
|
+
data: {
|
|
312
|
+
marimo: {
|
|
313
|
+
op: "completed-run",
|
|
314
|
+
run_id: "r1",
|
|
315
|
+
outcome: "succeeded",
|
|
316
|
+
error: null,
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
expect(store.get(initialRunCompletedAtom)).toBe(true);
|
|
321
|
+
expect(errors).not.toHaveBeenCalled();
|
|
322
|
+
} finally {
|
|
323
|
+
errors.mockRestore();
|
|
324
|
+
}
|
|
351
325
|
});
|
|
352
326
|
});
|
|
353
327
|
|
|
@@ -356,15 +330,13 @@ it.each(["kernel-ready", "reconnected"])(
|
|
|
356
330
|
async (op) => {
|
|
357
331
|
const store = createStore();
|
|
358
332
|
store.set(connectionAtom, { state: ConnectionState.CONNECTING });
|
|
359
|
-
vi.mocked(
|
|
360
|
-
vi.mocked(
|
|
333
|
+
vi.mocked(createConnectionTransport).mockClear();
|
|
334
|
+
vi.mocked(createConnectionTransport).mockReturnValue(
|
|
361
335
|
makeTransport(WebSocket.OPEN),
|
|
362
336
|
);
|
|
363
|
-
vi.mocked(useRuntimeManager).mockReturnValue(
|
|
364
|
-
makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
|
|
365
|
-
);
|
|
337
|
+
vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
|
|
366
338
|
renderConnectionHook(store);
|
|
367
|
-
const options =
|
|
339
|
+
const options = getTransportHandlers();
|
|
368
340
|
|
|
369
341
|
await act(async () => {
|
|
370
342
|
await options.onOpen(new Event("open"));
|
|
@@ -401,59 +373,6 @@ it.each(["kernel-ready", "reconnected"])(
|
|
|
401
373
|
},
|
|
402
374
|
);
|
|
403
375
|
|
|
404
|
-
it.each([true, false])(
|
|
405
|
-
"keeps attachments only when kernel-ready resumes the session: %s",
|
|
406
|
-
(resumed) => {
|
|
407
|
-
const store = createStore();
|
|
408
|
-
const attachments: Attachment[] = [
|
|
409
|
-
{
|
|
410
|
-
id: "a1",
|
|
411
|
-
kind: "agent",
|
|
412
|
-
name: "Pi",
|
|
413
|
-
since: "2026-10-14T00:00:00Z",
|
|
414
|
-
notebook_id: "nb-test",
|
|
415
|
-
identity: "pi-thread",
|
|
416
|
-
},
|
|
417
|
-
];
|
|
418
|
-
store.set(attachmentsAtom, attachments);
|
|
419
|
-
vi.mocked(useConnectionTransport).mockReturnValue(
|
|
420
|
-
makeTransport(WebSocket.OPEN),
|
|
421
|
-
);
|
|
422
|
-
vi.mocked(useRuntimeManager).mockReturnValue(
|
|
423
|
-
makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
|
|
424
|
-
);
|
|
425
|
-
renderConnectionHook(store);
|
|
426
|
-
const options = vi.mocked(useConnectionTransport).mock.calls.at(-1)![0];
|
|
427
|
-
act(() =>
|
|
428
|
-
options.onMessage(
|
|
429
|
-
new MessageEvent("message", {
|
|
430
|
-
data: JSON.stringify({
|
|
431
|
-
op: "kernel-ready",
|
|
432
|
-
data: {
|
|
433
|
-
op: "kernel-ready",
|
|
434
|
-
cell_ids: [],
|
|
435
|
-
codes: [],
|
|
436
|
-
names: [],
|
|
437
|
-
configs: [],
|
|
438
|
-
layout: null,
|
|
439
|
-
resumed,
|
|
440
|
-
ui_values: {},
|
|
441
|
-
last_executed_code: {},
|
|
442
|
-
last_execution_time: {},
|
|
443
|
-
app_config: { width: "normal" },
|
|
444
|
-
kiosk: false,
|
|
445
|
-
capabilities: { terminal: false },
|
|
446
|
-
auto_instantiated: true,
|
|
447
|
-
consumer_capabilities: { edit: true, interact: true },
|
|
448
|
-
},
|
|
449
|
-
}),
|
|
450
|
-
}),
|
|
451
|
-
),
|
|
452
|
-
);
|
|
453
|
-
expect(store.get(attachmentsAtom)).toEqual(resumed ? attachments : []);
|
|
454
|
-
},
|
|
455
|
-
);
|
|
456
|
-
|
|
457
376
|
describe("connection notice", () => {
|
|
458
377
|
beforeEach(() => vi.useFakeTimers());
|
|
459
378
|
afterEach(() => vi.useRealTimers());
|
|
@@ -462,15 +381,13 @@ describe("connection notice", () => {
|
|
|
462
381
|
const store = createStore();
|
|
463
382
|
store.set(connectionAtom, { state: ConnectionState.CONNECTING });
|
|
464
383
|
const transport = makeTransport(WebSocket.OPEN);
|
|
465
|
-
vi.mocked(
|
|
466
|
-
vi.mocked(useRuntimeManager).mockReturnValue(
|
|
467
|
-
makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
|
|
468
|
-
);
|
|
384
|
+
vi.mocked(createConnectionTransport).mockReturnValue(transport);
|
|
385
|
+
vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
|
|
469
386
|
const Connection = () => {
|
|
470
|
-
const { reconnect } =
|
|
471
|
-
sessionId:
|
|
387
|
+
const { reconnect } = useNotebookSession({
|
|
388
|
+
sessionId: SESSION_ID,
|
|
472
389
|
autoInstantiate: false,
|
|
473
|
-
setCells: ()
|
|
390
|
+
setCells: vi.fn(),
|
|
474
391
|
});
|
|
475
392
|
return (
|
|
476
393
|
<ConnectionNotice
|
|
@@ -486,7 +403,7 @@ describe("connection notice", () => {
|
|
|
486
403
|
</ErrorBoundary>
|
|
487
404
|
</JotaiProvider>,
|
|
488
405
|
);
|
|
489
|
-
const options =
|
|
406
|
+
const options = getTransportHandlers();
|
|
490
407
|
const send = (data: NotificationPayload["data"]) =>
|
|
491
408
|
act(() => {
|
|
492
409
|
options.onMessage(
|
|
@@ -675,15 +592,13 @@ describe("connection notice", () => {
|
|
|
675
592
|
|
|
676
593
|
it("replaces environment state from snapshots, then continues live progress", () => {
|
|
677
594
|
const store = createStore();
|
|
678
|
-
vi.mocked(
|
|
679
|
-
vi.mocked(
|
|
595
|
+
vi.mocked(createConnectionTransport).mockClear();
|
|
596
|
+
vi.mocked(createConnectionTransport).mockReturnValue(
|
|
680
597
|
makeTransport(WebSocket.OPEN),
|
|
681
598
|
);
|
|
682
|
-
vi.mocked(useRuntimeManager).mockReturnValue(
|
|
683
|
-
makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
|
|
684
|
-
);
|
|
599
|
+
vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
|
|
685
600
|
renderConnectionHook(store);
|
|
686
|
-
const options =
|
|
601
|
+
const options = getTransportHandlers();
|
|
687
602
|
function receive(data: NotificationPayload["data"]) {
|
|
688
603
|
act(() =>
|
|
689
604
|
options?.onMessage(
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { CloseDecision } from "../host/connection";
|
|
4
|
+
import { Logger } from "@/utils/Logger";
|
|
5
|
+
import { TRANSPORT_EXHAUSTED_REASON } from "./transports/ws";
|
|
6
|
+
import { ConnectionState, WebSocketClosedReason } from "./types";
|
|
7
|
+
|
|
8
|
+
// Keep server reasons in sync with ws_endpoint.py and ws/*.py.
|
|
9
|
+
// SSE carries the same reasons in close events. Unclassified reasons retry.
|
|
10
|
+
export function classifyCloseEvent(event: { reason?: string }): CloseDecision {
|
|
11
|
+
switch (event.reason) {
|
|
12
|
+
case TRANSPORT_EXHAUSTED_REASON:
|
|
13
|
+
return {
|
|
14
|
+
kind: "gave-up",
|
|
15
|
+
status: {
|
|
16
|
+
state: ConnectionState.CLOSED,
|
|
17
|
+
code: WebSocketClosedReason.KERNEL_DISCONNECTED,
|
|
18
|
+
reason: "kernel not found",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
case "MARIMO_NO_FILE_KEY":
|
|
22
|
+
case "MARIMO_NO_SESSION_ID":
|
|
23
|
+
case "MARIMO_NO_SESSION":
|
|
24
|
+
case "MARIMO_SHUTDOWN":
|
|
25
|
+
return {
|
|
26
|
+
kind: "terminal",
|
|
27
|
+
status: {
|
|
28
|
+
state: ConnectionState.CLOSED,
|
|
29
|
+
code: WebSocketClosedReason.KERNEL_DISCONNECTED,
|
|
30
|
+
reason: "kernel not found",
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
case "MARIMO_UNAUTHORIZED":
|
|
34
|
+
return {
|
|
35
|
+
kind: "terminal",
|
|
36
|
+
status: {
|
|
37
|
+
state: ConnectionState.CLOSED,
|
|
38
|
+
code: WebSocketClosedReason.KERNEL_DISCONNECTED,
|
|
39
|
+
reason: "not authorized",
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
case "MARIMO_KIOSK_NOT_ALLOWED":
|
|
43
|
+
return {
|
|
44
|
+
kind: "terminal",
|
|
45
|
+
status: {
|
|
46
|
+
state: ConnectionState.CLOSED,
|
|
47
|
+
code: WebSocketClosedReason.KERNEL_DISCONNECTED,
|
|
48
|
+
reason: "kiosk mode is not available for this session",
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
case "MARIMO_KERNEL_STARTUP_ERROR":
|
|
52
|
+
return {
|
|
53
|
+
kind: "terminal",
|
|
54
|
+
status: {
|
|
55
|
+
state: ConnectionState.CLOSED,
|
|
56
|
+
code: WebSocketClosedReason.KERNEL_STARTUP_ERROR,
|
|
57
|
+
reason: "Failed to start kernel sandbox",
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
default:
|
|
61
|
+
if (event.reason) {
|
|
62
|
+
Logger.warn("Unrecognized connection close reason", event.reason);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
kind: "retry",
|
|
68
|
+
status: { state: ConnectionState.CONNECTING },
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { atom } from "jotai";
|
|
4
|
+
import type { NotebookClient } from "../host/notebook-client";
|
|
5
|
+
|
|
6
|
+
/** The client owned by the mounted notebook session. */
|
|
7
|
+
export const notebookClientAtom = atom<NotebookClient | null>(null);
|
|
@@ -90,6 +90,23 @@ describe("SseTransport", () => {
|
|
|
90
90
|
expect(messages[1].data).toBe("line1\nline2");
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
+
it("preserves host event names and cursors without carrying IDs into snapshots", async () => {
|
|
94
|
+
const { stream, push } = createControllableStream();
|
|
95
|
+
fetchMock.mockResolvedValueOnce(sseResponse(stream));
|
|
96
|
+
transport.reconnect();
|
|
97
|
+
await vi.waitFor(() => expect(opens).toHaveLength(1));
|
|
98
|
+
|
|
99
|
+
push("id: cursor-1\nevent: rea");
|
|
100
|
+
push("dy\ndata: {}\n\n: keep-alive\n\nevent: reset\ndata: {}\n\n");
|
|
101
|
+
push('event: marimo\ndata: {"marimo":\ndata: {"op":"reconnected"}}\n\n');
|
|
102
|
+
await vi.waitFor(() => expect(messages).toHaveLength(3));
|
|
103
|
+
expect(messages.map((message) => JSON.parse(message.data))).toEqual([
|
|
104
|
+
{ event: "ready", id: "cursor-1", data: {} },
|
|
105
|
+
{ event: "reset", data: {} },
|
|
106
|
+
{ event: "marimo", data: { marimo: { op: "reconnected" } } },
|
|
107
|
+
]);
|
|
108
|
+
});
|
|
109
|
+
|
|
93
110
|
it("ignores keep-alive comments", async () => {
|
|
94
111
|
const { stream, push } = createControllableStream();
|
|
95
112
|
fetchMock.mockResolvedValueOnce(sseResponse(stream));
|
|
@@ -108,8 +125,6 @@ describe("SseTransport", () => {
|
|
|
108
125
|
transport.reconnect();
|
|
109
126
|
await vi.waitFor(() => expect(opens).toHaveLength(1));
|
|
110
127
|
|
|
111
|
-
// Stop retrying when the consumer treats the close as terminal,
|
|
112
|
-
// mirroring useMarimoKernelConnection's terminal path.
|
|
113
128
|
transport.addEventListener("close", () => transport.close());
|
|
114
129
|
push('event: close\ndata: {"code": 1000, "reason": "MARIMO_SHUTDOWN"}\n\n');
|
|
115
130
|
await vi.waitFor(() => expect(closes).toHaveLength(1));
|
|
@@ -146,8 +161,7 @@ describe("SseTransport", () => {
|
|
|
146
161
|
fetchMock
|
|
147
162
|
.mockResolvedValueOnce(sseResponse(first.stream))
|
|
148
163
|
.mockResolvedValueOnce(sseResponse(second.stream));
|
|
149
|
-
//
|
|
150
|
-
// synchronously from inside the close handler.
|
|
164
|
+
// Reconnect synchronously from the close handler.
|
|
151
165
|
transport.addEventListener("close", () => transport.reconnect());
|
|
152
166
|
|
|
153
167
|
transport.reconnect();
|