@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
|
@@ -8,21 +8,24 @@ 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";
|
|
15
15
|
import { cellId } from "@/__tests__/branded";
|
|
16
|
-
import {
|
|
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
|
-
import
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
import {
|
|
22
|
+
type CellOutputMessage,
|
|
23
|
+
type ErrorMessageOutput,
|
|
24
|
+
ERROR_MESSAGE_INSTRUCTIONS,
|
|
24
25
|
} from "@/core/cells/error-report";
|
|
25
|
-
import {
|
|
26
|
+
import { pairConfigAtom } from "@/core/config/pair";
|
|
27
|
+
import { notebookStateFromSession } from "@/core/cells/session";
|
|
28
|
+
import { createNotebookClient } from "@/core/host/notebook-client";
|
|
26
29
|
import type { MarimoError, OutputMessage } from "@/core/kernel/messages";
|
|
27
30
|
import {
|
|
28
31
|
type StableSessionId,
|
|
@@ -30,7 +33,8 @@ import {
|
|
|
30
33
|
} from "@/core/kernel/session";
|
|
31
34
|
import { API } from "@/core/network/api";
|
|
32
35
|
import { connectionAtom } from "@/core/network/connection";
|
|
33
|
-
import {
|
|
36
|
+
import { notebookClientAtom } from "@/core/websocket/state";
|
|
37
|
+
import { ConnectionState } from "@/core/websocket/types";
|
|
34
38
|
import { Deferred } from "@/utils/Deferred";
|
|
35
39
|
import { HTTPError } from "@/utils/errors";
|
|
36
40
|
import { MarimoErrorOutput } from "../../output/MarimoErrorOutput";
|
|
@@ -46,6 +50,8 @@ vi.mock("@/components/editor/chrome/wrapper/useOpenAiAssistant", () => ({
|
|
|
46
50
|
|
|
47
51
|
const CELL_ID = cellId("test-cell");
|
|
48
52
|
const STABLE_ID = "stable/test" as StableSessionId;
|
|
53
|
+
const attachmentsAtom = atom<Attachment[]>([]);
|
|
54
|
+
|
|
49
55
|
const AGENT: Attachment = {
|
|
50
56
|
id: "a1",
|
|
51
57
|
kind: "agent",
|
|
@@ -57,11 +63,15 @@ const AGENT: Attachment = {
|
|
|
57
63
|
const ROUTE = "/pair/notebooks/stable%2Ftest/messages";
|
|
58
64
|
const MESSAGE = "df is not defined";
|
|
59
65
|
const CODE = "df.head()";
|
|
60
|
-
const REPORT:
|
|
66
|
+
const REPORT: CellOutputMessage<ErrorMessageOutput> = {
|
|
61
67
|
type: "cell-output",
|
|
62
68
|
cell_id: CELL_ID,
|
|
63
69
|
code: CODE,
|
|
64
|
-
output: {
|
|
70
|
+
output: {
|
|
71
|
+
type: "error",
|
|
72
|
+
instructions: ERROR_MESSAGE_INSTRUCTIONS,
|
|
73
|
+
message: MESSAGE,
|
|
74
|
+
},
|
|
65
75
|
};
|
|
66
76
|
const ERRORS: MarimoError[] = [
|
|
67
77
|
{
|
|
@@ -86,7 +96,12 @@ function renderButton({
|
|
|
86
96
|
} = {}) {
|
|
87
97
|
const store = createStore();
|
|
88
98
|
store.set(connectionAtom, { state: ConnectionState.OPEN });
|
|
89
|
-
store.set(
|
|
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
|
+
});
|
|
90
105
|
store.set(attachmentsAtom, attachments);
|
|
91
106
|
store.set(stableSessionIdAtom, stableId);
|
|
92
107
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
@@ -105,9 +120,15 @@ describe("SendErrorReportButton", () => {
|
|
|
105
120
|
beforeEach(() => {
|
|
106
121
|
vi.clearAllMocks();
|
|
107
122
|
vi.mocked(API.post).mockResolvedValue("");
|
|
123
|
+
vi.mocked(toast).mockReturnValue({
|
|
124
|
+
id: "notice",
|
|
125
|
+
dismiss: vi.fn(),
|
|
126
|
+
update: vi.fn(),
|
|
127
|
+
upsert: vi.fn(),
|
|
128
|
+
});
|
|
108
129
|
});
|
|
109
130
|
|
|
110
|
-
it("hides
|
|
131
|
+
it("hides when host is disabled without building content", () => {
|
|
111
132
|
const getContent = vi.fn(() => REPORT);
|
|
112
133
|
expect(
|
|
113
134
|
renderButton({
|
|
@@ -138,7 +159,10 @@ describe("SendErrorReportButton", () => {
|
|
|
138
159
|
expect(getContent).not.toHaveBeenCalled();
|
|
139
160
|
send();
|
|
140
161
|
await waitFor(() =>
|
|
141
|
-
expect(toast).toHaveBeenCalledWith({
|
|
162
|
+
expect(toast).toHaveBeenCalledWith({
|
|
163
|
+
title: "Sent to Pi, Claude Code",
|
|
164
|
+
duration: 3000,
|
|
165
|
+
}),
|
|
142
166
|
);
|
|
143
167
|
expect(API.post).toHaveBeenCalledWith(ROUTE, REPORT);
|
|
144
168
|
expect(getContent).toHaveBeenCalledOnce();
|
|
@@ -162,26 +186,6 @@ describe("SendErrorReportButton", () => {
|
|
|
162
186
|
expect(screen.getByRole("button")).toBeDisabled();
|
|
163
187
|
});
|
|
164
188
|
|
|
165
|
-
it.each([
|
|
166
|
-
{ state: ConnectionState.CONNECTING },
|
|
167
|
-
{ state: ConnectionState.CLOSING },
|
|
168
|
-
{ state: ConnectionState.NOT_STARTED },
|
|
169
|
-
{
|
|
170
|
-
state: ConnectionState.CLOSED,
|
|
171
|
-
code: WebSocketClosedReason.KERNEL_DISCONNECTED,
|
|
172
|
-
reason: "offline",
|
|
173
|
-
},
|
|
174
|
-
])(
|
|
175
|
-
"does not send from a stale snapshot while the browser is %j",
|
|
176
|
-
(connection) => {
|
|
177
|
-
const { store } = renderButton();
|
|
178
|
-
act(() => store.set(connectionAtom, connection));
|
|
179
|
-
expect(screen.getByRole("button")).toBeDisabled();
|
|
180
|
-
send();
|
|
181
|
-
expect(API.post).not.toHaveBeenCalled();
|
|
182
|
-
},
|
|
183
|
-
);
|
|
184
|
-
|
|
185
189
|
it.each([
|
|
186
190
|
[new HTTPError(409, "Conflict"), "No agent attached"],
|
|
187
191
|
[new HTTPError(500, "Server error"), "Could not send error"],
|
|
@@ -190,12 +194,20 @@ describe("SendErrorReportButton", () => {
|
|
|
190
194
|
renderButton();
|
|
191
195
|
send();
|
|
192
196
|
await waitFor(() =>
|
|
193
|
-
expect(toast).toHaveBeenCalledWith({
|
|
197
|
+
expect(toast).toHaveBeenCalledWith({
|
|
198
|
+
variant: "danger",
|
|
199
|
+
title,
|
|
200
|
+
description: error.message,
|
|
201
|
+
duration: 5000,
|
|
202
|
+
}),
|
|
194
203
|
);
|
|
195
204
|
expect(screen.getByRole("button")).toBeEnabled();
|
|
196
205
|
send();
|
|
197
206
|
await waitFor(() =>
|
|
198
|
-
expect(toast).toHaveBeenCalledWith({
|
|
207
|
+
expect(toast).toHaveBeenCalledWith({
|
|
208
|
+
title: "Sent to Pi",
|
|
209
|
+
duration: 3000,
|
|
210
|
+
}),
|
|
199
211
|
);
|
|
200
212
|
expect(API.post).toHaveBeenCalledTimes(2);
|
|
201
213
|
expect(API.post).toHaveBeenLastCalledWith(ROUTE, REPORT);
|
|
@@ -222,6 +234,7 @@ describe("SendErrorReportButton", () => {
|
|
|
222
234
|
...REPORT,
|
|
223
235
|
output: {
|
|
224
236
|
type: "error",
|
|
237
|
+
instructions: ERROR_MESSAGE_INSTRUCTIONS,
|
|
225
238
|
message: MESSAGE,
|
|
226
239
|
traceback: "Traceback\nNameError: df is not defined",
|
|
227
240
|
},
|
|
@@ -264,6 +277,7 @@ describe("SendErrorReportButton", () => {
|
|
|
264
277
|
...REPORT,
|
|
265
278
|
output: {
|
|
266
279
|
type: "error",
|
|
280
|
+
instructions: ERROR_MESSAGE_INSTRUCTIONS,
|
|
267
281
|
message: "ValueError: bad <value>",
|
|
268
282
|
traceback: "Traceback\nValueError: bad <value>",
|
|
269
283
|
},
|
|
@@ -271,6 +285,90 @@ describe("SendErrorReportButton", () => {
|
|
|
271
285
|
);
|
|
272
286
|
});
|
|
273
287
|
|
|
288
|
+
it("sends plain code-mode traceback text without parsing it as HTML", async () => {
|
|
289
|
+
const traceback =
|
|
290
|
+
' File "<cell>", line 1\nValueError: bad <value> & <other>';
|
|
291
|
+
renderButton({
|
|
292
|
+
content: (
|
|
293
|
+
<OutputRenderer
|
|
294
|
+
cellId={CELL_ID}
|
|
295
|
+
message={{
|
|
296
|
+
channel: "stderr",
|
|
297
|
+
mimetype: "application/vnd.marimo+traceback",
|
|
298
|
+
traceback_format: "text",
|
|
299
|
+
code: CODE,
|
|
300
|
+
data: traceback,
|
|
301
|
+
}}
|
|
302
|
+
/>
|
|
303
|
+
),
|
|
304
|
+
});
|
|
305
|
+
expect(screen.getByText(/ValueError: bad <value>/)).toHaveTextContent(
|
|
306
|
+
"<cell>",
|
|
307
|
+
);
|
|
308
|
+
send();
|
|
309
|
+
await waitFor(() =>
|
|
310
|
+
expect(API.post).toHaveBeenCalledWith(ROUTE, {
|
|
311
|
+
...REPORT,
|
|
312
|
+
output: {
|
|
313
|
+
type: "error",
|
|
314
|
+
instructions: ERROR_MESSAGE_INSTRUCTIONS,
|
|
315
|
+
message: "ValueError: bad <value> & <other>",
|
|
316
|
+
traceback,
|
|
317
|
+
},
|
|
318
|
+
}),
|
|
319
|
+
);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it("sends the restored exception type and complete traceback", async () => {
|
|
323
|
+
const state = notebookStateFromSession(
|
|
324
|
+
{
|
|
325
|
+
version: "1",
|
|
326
|
+
metadata: { marimo_version: "1", script_metadata_hash: null },
|
|
327
|
+
cells: [
|
|
328
|
+
{
|
|
329
|
+
id: CELL_ID,
|
|
330
|
+
code_hash: null,
|
|
331
|
+
console: [],
|
|
332
|
+
outputs: [
|
|
333
|
+
{
|
|
334
|
+
type: "error",
|
|
335
|
+
ename: "ValueError",
|
|
336
|
+
evalue: "cached failure",
|
|
337
|
+
traceback: [
|
|
338
|
+
"<pre>Traceback",
|
|
339
|
+
" failed()\n",
|
|
340
|
+
"ValueError: cached failure</pre>",
|
|
341
|
+
],
|
|
342
|
+
code: CODE,
|
|
343
|
+
},
|
|
344
|
+
],
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
},
|
|
348
|
+
null,
|
|
349
|
+
);
|
|
350
|
+
const message = state?.cellRuntime[CELL_ID].output;
|
|
351
|
+
expect(message).toBeDefined();
|
|
352
|
+
renderButton({
|
|
353
|
+
content: message && <OutputRenderer cellId={CELL_ID} message={message} />,
|
|
354
|
+
});
|
|
355
|
+
send();
|
|
356
|
+
await waitFor(() =>
|
|
357
|
+
expect(API.post).toHaveBeenCalledWith(ROUTE, {
|
|
358
|
+
...REPORT,
|
|
359
|
+
output: {
|
|
360
|
+
type: "error",
|
|
361
|
+
instructions: ERROR_MESSAGE_INSTRUCTIONS,
|
|
362
|
+
message: "cached failure",
|
|
363
|
+
traceback: "Traceback\n failed()\nValueError: cached failure",
|
|
364
|
+
},
|
|
365
|
+
}),
|
|
366
|
+
);
|
|
367
|
+
expect(message?.data).toEqual([
|
|
368
|
+
expect.objectContaining({ exception_type: "ValueError" }),
|
|
369
|
+
]);
|
|
370
|
+
});
|
|
371
|
+
|
|
274
372
|
it("sends updated content after the error changes", async () => {
|
|
275
373
|
const { rerender } = renderButton();
|
|
276
374
|
const updated = {
|
|
@@ -385,7 +483,11 @@ describe("SendErrorReportButton", () => {
|
|
|
385
483
|
type: "cell-output",
|
|
386
484
|
cell_id: CELL_ID,
|
|
387
485
|
code: "x + 1",
|
|
388
|
-
output: {
|
|
486
|
+
output: {
|
|
487
|
+
type: "error",
|
|
488
|
+
instructions: ERROR_MESSAGE_INSTRUCTIONS,
|
|
489
|
+
message: "Ancestor failed",
|
|
490
|
+
},
|
|
389
491
|
}),
|
|
390
492
|
);
|
|
391
493
|
});
|
|
@@ -408,6 +510,7 @@ describe("SendErrorReportButton", () => {
|
|
|
408
510
|
code: "slow()",
|
|
409
511
|
output: {
|
|
410
512
|
type: "error",
|
|
513
|
+
instructions: ERROR_MESSAGE_INSTRUCTIONS,
|
|
411
514
|
message: "This cell was interrupted and needs to be re-run",
|
|
412
515
|
},
|
|
413
516
|
}),
|
|
@@ -4,27 +4,27 @@ import { useAtomValue } from "jotai";
|
|
|
4
4
|
import { SendIcon } from "lucide-react";
|
|
5
5
|
import { useState } from "react";
|
|
6
6
|
import { Button } from "@/components/ui/button";
|
|
7
|
-
import { toast } from "@/components/ui/use-toast";
|
|
8
7
|
import {
|
|
9
8
|
agentAttachmentsAtom,
|
|
10
9
|
attachmentLabel,
|
|
11
10
|
} from "@/core/attachments/state";
|
|
12
|
-
import {
|
|
11
|
+
import { agentToast } from "@/core/attachments/toast";
|
|
12
|
+
import { pairConfigAtom } from "@/core/config/pair";
|
|
13
13
|
import { stableSessionIdAtom } from "@/core/kernel/session";
|
|
14
14
|
import { API } from "@/core/network/api";
|
|
15
|
-
import { HTTPError } from "@/utils/errors";
|
|
15
|
+
import { HTTPError, prettyError } from "@/utils/errors";
|
|
16
16
|
|
|
17
17
|
export function SendErrorReportButton({
|
|
18
18
|
getContent,
|
|
19
19
|
}: {
|
|
20
20
|
getContent: () => unknown;
|
|
21
21
|
}) {
|
|
22
|
-
const
|
|
22
|
+
const hostEnabled = useAtomValue(pairConfigAtom)?.hostEnabled;
|
|
23
23
|
const agents = useAtomValue(agentAttachmentsAtom);
|
|
24
24
|
const stableSessionId = useAtomValue(stableSessionIdAtom);
|
|
25
25
|
const [sending, setSending] = useState(false);
|
|
26
26
|
|
|
27
|
-
if (!
|
|
27
|
+
if (!hostEnabled) {
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -38,14 +38,17 @@ export function SendErrorReportButton({
|
|
|
38
38
|
`/pair/notebooks/${encodeURIComponent(stableSessionId)}/messages`,
|
|
39
39
|
getContent(),
|
|
40
40
|
);
|
|
41
|
-
|
|
41
|
+
agentToast({
|
|
42
|
+
title: `Sent to ${agents.map(attachmentLabel).join(", ")}`,
|
|
43
|
+
});
|
|
42
44
|
} catch (error) {
|
|
43
|
-
|
|
45
|
+
agentToast({
|
|
44
46
|
variant: "danger",
|
|
45
47
|
title:
|
|
46
48
|
error instanceof HTTPError && error.status === 409
|
|
47
49
|
? "No agent attached"
|
|
48
50
|
: "Could not send error",
|
|
51
|
+
description: prettyError(error),
|
|
49
52
|
});
|
|
50
53
|
} finally {
|
|
51
54
|
setSending(false);
|
|
@@ -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 (
|
|
@@ -35,6 +35,7 @@ import { isWasm } from "@/core/wasm/utils";
|
|
|
35
35
|
import { renderHTML } from "@/plugins/core/RenderHTML";
|
|
36
36
|
import { sanitizeHtml } from "@/plugins/core/sanitize-html";
|
|
37
37
|
import { copyToClipboard } from "@/utils/copy";
|
|
38
|
+
import { parseHtmlContent } from "@/utils/dom";
|
|
38
39
|
import {
|
|
39
40
|
containsMangledLocal,
|
|
40
41
|
splitMangledLocals,
|
|
@@ -54,29 +55,38 @@ import type { OnRefactorWithAI } from "../Output";
|
|
|
54
55
|
interface Props {
|
|
55
56
|
cellId: CellId | undefined;
|
|
56
57
|
traceback: string;
|
|
58
|
+
tracebackFormat?: "html" | "text";
|
|
57
59
|
code?: string;
|
|
58
60
|
onRefactorWithAI?: OnRefactorWithAI;
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
/**
|
|
62
|
-
* List of errors due to violations of Marimo semantics.
|
|
63
|
-
*/
|
|
63
|
+
/** Render a traceback and actions for its captured execution. */
|
|
64
64
|
export const MarimoTracebackOutput = ({
|
|
65
65
|
onRefactorWithAI,
|
|
66
66
|
traceback,
|
|
67
|
+
tracebackFormat = "html",
|
|
67
68
|
cellId,
|
|
68
69
|
code,
|
|
69
70
|
}: Props): JSX.Element => {
|
|
70
|
-
const htmlTraceback =
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
const htmlTraceback =
|
|
72
|
+
tracebackFormat === "text" ? (
|
|
73
|
+
<pre>{traceback}</pre>
|
|
74
|
+
) : (
|
|
75
|
+
renderHTML({
|
|
76
|
+
html: traceback,
|
|
77
|
+
additionalReplacements: [
|
|
78
|
+
replaceTracebackFilenames,
|
|
79
|
+
replaceTracebackPrefix,
|
|
80
|
+
replaceMangledLocal,
|
|
81
|
+
],
|
|
82
|
+
})
|
|
83
|
+
);
|
|
78
84
|
|
|
79
|
-
const
|
|
85
|
+
const textTraceback =
|
|
86
|
+
tracebackFormat === "html"
|
|
87
|
+
? parseHtmlContent(sanitizeHtml(traceback))
|
|
88
|
+
: traceback;
|
|
89
|
+
const lastTracebackLine = lastLine(textTraceback);
|
|
80
90
|
const aiFeaturesEnabled = useAtomValue(aiFeaturesEnabledAtom);
|
|
81
91
|
const openAiAssistant = useOpenAiAssistant();
|
|
82
92
|
|
|
@@ -137,7 +147,9 @@ export const MarimoTracebackOutput = ({
|
|
|
137
147
|
<div className="flex gap-2">
|
|
138
148
|
{cellId && lastTracebackLine && (
|
|
139
149
|
<SendErrorReportButton
|
|
140
|
-
getContent={() =>
|
|
150
|
+
getContent={() =>
|
|
151
|
+
buildErrorReport(cellId, { code, traceback, tracebackFormat })
|
|
152
|
+
}
|
|
141
153
|
/>
|
|
142
154
|
)}
|
|
143
155
|
{showAIFix && (
|
|
@@ -195,11 +207,7 @@ export const MarimoTracebackOutput = ({
|
|
|
195
207
|
</DropdownMenuItem>
|
|
196
208
|
<DropdownMenuItem
|
|
197
209
|
onClick={() => {
|
|
198
|
-
|
|
199
|
-
const div = document.createElement("div");
|
|
200
|
-
div.innerHTML = sanitizeHtml(traceback);
|
|
201
|
-
const textContent = div.textContent || "";
|
|
202
|
-
copyToClipboard(textContent);
|
|
210
|
+
copyToClipboard(textTraceback);
|
|
203
211
|
}}
|
|
204
212
|
>
|
|
205
213
|
<CopyIcon className="h-4 w-4 mr-2" />
|
|
@@ -214,9 +222,7 @@ export const MarimoTracebackOutput = ({
|
|
|
214
222
|
};
|
|
215
223
|
|
|
216
224
|
function lastLine(text: string): string {
|
|
217
|
-
const
|
|
218
|
-
el.innerHTML = sanitizeHtml(text);
|
|
219
|
-
const lines = el.textContent?.split("\n").filter(Boolean);
|
|
225
|
+
const lines = text.split("\n").filter(Boolean);
|
|
220
226
|
return lines?.at(-1) || "";
|
|
221
227
|
}
|
|
222
228
|
|
|
@@ -242,7 +248,7 @@ export const replaceTracebackFilenames = (domNode: DOMNode) => {
|
|
|
242
248
|
<Tooltip content={tooltipContent}>
|
|
243
249
|
<button
|
|
244
250
|
type="button"
|
|
245
|
-
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"
|
|
246
252
|
>
|
|
247
253
|
<BugPlayIcon
|
|
248
254
|
onClick={() => {
|
|
@@ -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";
|
|
@@ -49,31 +49,21 @@ export const CONSOLE_CLEAR_DEBOUNCE_MS = 200;
|
|
|
49
49
|
*/
|
|
50
50
|
function useDebouncedConsoleOutputs<T>(outputs: T[]): T[] {
|
|
51
51
|
const [debouncedOutputs, setDebouncedOutputs] = useState(outputs);
|
|
52
|
-
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
53
52
|
|
|
54
|
-
// Non-empty outputs: apply immediately
|
|
53
|
+
// Non-empty outputs: apply immediately. Effect cleanup cancels pending clears.
|
|
55
54
|
if (outputs.length > 0 && debouncedOutputs !== outputs) {
|
|
56
|
-
if (timerRef.current !== null) {
|
|
57
|
-
clearTimeout(timerRef.current);
|
|
58
|
-
timerRef.current = null;
|
|
59
|
-
}
|
|
60
55
|
setDebouncedOutputs(outputs);
|
|
61
56
|
}
|
|
62
57
|
|
|
63
58
|
// Empty outputs: delay the clear so new outputs can arrive first
|
|
64
59
|
useEffect(() => {
|
|
65
|
-
if (outputs.length
|
|
66
|
-
|
|
67
|
-
timerRef.current = null;
|
|
68
|
-
setDebouncedOutputs([]);
|
|
69
|
-
}, CONSOLE_CLEAR_DEBOUNCE_MS);
|
|
60
|
+
if (outputs.length > 0) {
|
|
61
|
+
return;
|
|
70
62
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
};
|
|
63
|
+
const timer = setTimeout(() => {
|
|
64
|
+
setDebouncedOutputs([]);
|
|
65
|
+
}, CONSOLE_CLEAR_DEBOUNCE_MS);
|
|
66
|
+
return () => clearTimeout(timer);
|
|
77
67
|
}, [outputs]);
|
|
78
68
|
|
|
79
69
|
return debouncedOutputs;
|
|
@@ -514,7 +514,7 @@ const MarimoFileComponent = ({ file }: { file: MarimoFile }) => {
|
|
|
514
514
|
|
|
515
515
|
return (
|
|
516
516
|
<a
|
|
517
|
-
className="py-1.5 px-4 hover:bg-(--blue-2) hover:text-primary transition-
|
|
517
|
+
className="py-1.5 px-4 hover:bg-(--blue-2) hover:text-primary transition-colors duration-300 cursor-pointer group relative flex gap-4 items-center"
|
|
518
518
|
key={file.path}
|
|
519
519
|
href={href.toString()}
|
|
520
520
|
target={tabTarget(file.initializationId || file.path)}
|
|
@@ -542,7 +542,7 @@ const MarimoFileComponent = ({ file }: { file: MarimoFile }) => {
|
|
|
542
542
|
</div>
|
|
543
543
|
<ExternalLinkIcon
|
|
544
544
|
size={20}
|
|
545
|
-
className="group-hover:opacity-100 opacity-0 transition-
|
|
545
|
+
className="group-hover:opacity-100 opacity-0 transition-opacity duration-300 text-primary"
|
|
546
546
|
/>
|
|
547
547
|
</div>
|
|
548
548
|
{!!file.lastModified && (
|
|
@@ -617,14 +617,14 @@ const CreateNewNotebook: React.FC = () => {
|
|
|
617
617
|
<a
|
|
618
618
|
className="relative rounded-lg p-6 group
|
|
619
619
|
text-primary hover:bg-(--blue-2) shadow-md-solid shadow-accent border bg-(--blue-1)
|
|
620
|
-
transition-
|
|
620
|
+
transition-colors duration-300 cursor-pointer
|
|
621
621
|
"
|
|
622
622
|
href={url}
|
|
623
623
|
target="_blank"
|
|
624
624
|
rel="noreferrer"
|
|
625
625
|
>
|
|
626
626
|
<h2 className="text-lg font-semibold">Create a new notebook</h2>
|
|
627
|
-
<div className="group-hover:opacity-100 opacity-0 absolute right-5 top-0 bottom-0 rounded-lg flex items-center justify-center transition-
|
|
627
|
+
<div className="group-hover:opacity-100 opacity-0 absolute right-5 top-0 bottom-0 rounded-lg flex items-center justify-center transition-opacity duration-300">
|
|
628
628
|
<ExternalLinkIcon size={24} />
|
|
629
629
|
</div>
|
|
630
630
|
</a>
|
|
@@ -117,7 +117,7 @@ export const SlideCellView = ({ cell }: { cell: RuntimeCell }) => {
|
|
|
117
117
|
/>
|
|
118
118
|
<LanguageToggles
|
|
119
119
|
code={cell.code}
|
|
120
|
-
|
|
120
|
+
editorViewRef={editorViewRef}
|
|
121
121
|
currentLanguageAdapter={languageAdapter}
|
|
122
122
|
onAfterToggle={afterToggleLanguage}
|
|
123
123
|
className="flex items-center gap-1"
|
|
@@ -145,11 +145,11 @@ const TraceBlock: React.FC<{
|
|
|
145
145
|
isMostRecentRun: boolean;
|
|
146
146
|
chartPosition: ChartPosition;
|
|
147
147
|
theme: ResolvedTheme;
|
|
148
|
-
}> = ({ run, isMostRecentRun, chartPosition, isExpanded, theme }) => {
|
|
148
|
+
}> = ({ run, isMostRecentRun, chartPosition, isExpanded: expanded, theme }) => {
|
|
149
149
|
const setExpandedRuns = useSetAtom(expandedRunsAtom);
|
|
150
150
|
// We prefer the user's last click, but if they haven't clicked on this run,
|
|
151
151
|
// we expand the most recent run by default, otherwise we collapse it.
|
|
152
|
-
isExpanded =
|
|
152
|
+
const isExpanded = expanded ?? isMostRecentRun;
|
|
153
153
|
|
|
154
154
|
const onToggleExpanded = () => {
|
|
155
155
|
setExpandedRuns((prev) => {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
// @vitest-environment jsdom
|
|
3
|
+
|
|
4
|
+
import { act, render, renderHook } from "@testing-library/react";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { Toaster } from "@/components/ui/toaster";
|
|
7
|
+
import { clearToasts, toast, useToast } from "@/components/ui/use-toast";
|
|
8
|
+
import { agentToast } from "../toast";
|
|
9
|
+
|
|
10
|
+
describe("agent notifications", () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
vi.useFakeTimers();
|
|
13
|
+
clearToasts();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
act(() => {
|
|
18
|
+
clearToasts();
|
|
19
|
+
vi.runOnlyPendingTimers();
|
|
20
|
+
});
|
|
21
|
+
vi.useRealTimers();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it.each([
|
|
25
|
+
{ variant: undefined, duration: 3000 },
|
|
26
|
+
{ variant: "danger" as const, duration: 5000 },
|
|
27
|
+
])("expires while the window is blurred: %j", ({ variant, duration }) => {
|
|
28
|
+
const { result } = renderHook(useToast);
|
|
29
|
+
render(<Toaster />);
|
|
30
|
+
act(() => agentToast({ title: "Agent notice", variant }));
|
|
31
|
+
act(() => window.dispatchEvent(new Event("blur")));
|
|
32
|
+
act(() => vi.advanceTimersByTime(duration - 1));
|
|
33
|
+
expect(result.current.toasts[0].open).toBe(true);
|
|
34
|
+
act(() => vi.advanceTimersByTime(1));
|
|
35
|
+
expect(result.current.toasts[0].open).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("preserves pause behavior for ordinary notifications", () => {
|
|
39
|
+
const { result } = renderHook(useToast);
|
|
40
|
+
render(<Toaster />);
|
|
41
|
+
act(() => toast({ title: "Ordinary notice", duration: 3000 }));
|
|
42
|
+
act(() => window.dispatchEvent(new Event("blur")));
|
|
43
|
+
act(() => vi.advanceTimersByTime(5000));
|
|
44
|
+
expect(result.current.toasts[0].open).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("does not let an older timer dismiss a newer notification", () => {
|
|
48
|
+
const { result } = renderHook(useToast);
|
|
49
|
+
act(() => agentToast({ title: "First" }));
|
|
50
|
+
act(() => vi.advanceTimersByTime(1000));
|
|
51
|
+
act(() => agentToast({ title: "Second" }));
|
|
52
|
+
act(() => vi.advanceTimersByTime(2000));
|
|
53
|
+
expect(result.current.toasts[0].title).toBe("Second");
|
|
54
|
+
expect(result.current.toasts[0].open).toBe(true);
|
|
55
|
+
act(() => vi.advanceTimersByTime(1000));
|
|
56
|
+
expect(result.current.toasts[0].open).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import type { components } from "@marimo-team/marimo-api";
|
|
4
3
|
import { atom } from "jotai";
|
|
5
|
-
import {
|
|
4
|
+
import type { Attachment } from "@marimo-team/client/protocol";
|
|
5
|
+
import { notebookClientAtom } from "@/core/websocket/state";
|
|
6
6
|
|
|
7
|
-
export type Attachment
|
|
7
|
+
export type { Attachment } from "@marimo-team/client/protocol";
|
|
8
8
|
|
|
9
|
-
export const attachmentsAtom = atom<Attachment[]>([]);
|
|
10
9
|
export const agentAttachmentsAtom = atom((get) => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
return get(attachmentsAtom).filter(
|
|
10
|
+
const notebook = get(notebookClientAtom);
|
|
11
|
+
return (notebook ? get(notebook.attachmentsAtom) : []).filter(
|
|
15
12
|
(attachment) => attachment.kind === "agent",
|
|
16
13
|
);
|
|
17
14
|
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { toast } from "@/components/ui/use-toast";
|
|
4
|
+
|
|
5
|
+
export function agentToast(
|
|
6
|
+
options: Pick<
|
|
7
|
+
Parameters<typeof toast>[0],
|
|
8
|
+
"title" | "variant" | "description"
|
|
9
|
+
>,
|
|
10
|
+
) {
|
|
11
|
+
const duration = options.variant === "danger" ? 5000 : 3000;
|
|
12
|
+
const { dismiss } = toast({ ...options, duration });
|
|
13
|
+
// Agent notices must also expire while the user focuses the agent terminal.
|
|
14
|
+
window.setTimeout(dismiss, duration);
|
|
15
|
+
}
|