@marimo-team/frontend 0.19.3-dev8 → 0.19.4-dev0
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/assets/{CellStatus-BwPGnX3z.js → CellStatus--kUu6N2K.js} +1 -1
- package/dist/assets/{ConnectedDataExplorerComponent-KlUs_Sz3.js → ConnectedDataExplorerComponent-BKJwCHu7.js} +1 -1
- package/dist/assets/{ErrorBoundary-Drf1manw.js → ErrorBoundary-C7JBxSzd.js} +1 -1
- package/dist/assets/{ImperativeModal-q6QlC2aZ.js → ImperativeModal-DVhvP4lH.js} +1 -1
- package/dist/assets/{JsonOutput--AuyEErr.js → JsonOutput-BSGE-MRo.js} +5 -5
- package/dist/assets/{LazyAnyLanguageCodeMirror-jpEDlD0M.js → LazyAnyLanguageCodeMirror-Cp2punaU.js} +2 -2
- package/dist/assets/{MarimoErrorOutput-BZjY8e2w.js → MarimoErrorOutput-CX0SCJOZ.js} +2 -2
- package/dist/assets/{RenderHTML-BTLaM20B.js → RenderHTML-Do_PVqRy.js} +1 -1
- package/dist/assets/VisuallyHidden-B9t3FhTP.js +1 -0
- package/dist/assets/{add-cell-with-ai-BWWVs9qV.js → add-cell-with-ai-manh7kBT.js} +21 -21
- package/dist/assets/{add-database-form-Bw_YRH1r.js → add-database-form-CgkV0MRs.js} +2 -2
- package/dist/assets/agent-panel-D-OmT-rw.js +287 -0
- package/dist/assets/{ai-model-dropdown-BrUOgnWS.js → ai-model-dropdown-DzyBY5VA.js} +1 -1
- package/dist/assets/{alert-dialog-k5KxevGr.js → alert-dialog-jcHA5geR.js} +1 -1
- package/dist/assets/{any-language-editor-DQu1Tt2N.js → any-language-editor-Cm83E7D_.js} +1 -1
- package/dist/assets/{app-config-button-B8CXELx0.js → app-config-button-DC3alCuB.js} +1 -1
- package/dist/assets/button-B8cGZzP5.js +1 -0
- package/dist/assets/{cache-panel-C1So4Zu3.js → cache-panel-1FqnpB9y.js} +1 -1
- package/dist/assets/cell-editor-RHFZmO74.js +23 -0
- package/dist/assets/cell-link-Dqj_nfXA.js +1 -0
- package/dist/assets/{cells-DU3EySUd.js → cells-BNQUQiDS.js} +49 -49
- package/dist/assets/{chat-components-Bc9j9ls4.js → chat-components-CWiXtKu6.js} +1 -1
- package/dist/assets/{chat-display-BrTi6c8V.js → chat-display-CGnOamQG.js} +1 -1
- package/dist/assets/{chat-panel-8Dym5Gv3.js → chat-panel-Dh1M55c9.js} +2 -2
- package/dist/assets/client-CDjmJmVw.js +4 -0
- package/dist/assets/{column-preview-Ck6B_-sQ.js → column-preview-CKxT2s-S.js} +1 -1
- package/dist/assets/{command-B_minI8b.js → command-YPFTinLj.js} +1 -1
- package/dist/assets/{command-palette-BT3u6JBB.js → command-palette-7fVEhKGc.js} +1 -1
- package/dist/assets/common-DJkPpBxC.js +1 -0
- package/dist/assets/config-D6nhy4FA.js +1 -0
- package/dist/assets/context-DHfVoQfl.js +1 -0
- package/dist/assets/{copy-icon-B69c-352.js → copy-icon-jWsqdLn1.js} +1 -1
- package/dist/assets/{datasource-DCvPlnaJ.js → datasource-DerBLc6V.js} +2 -2
- package/dist/assets/{dependency-graph-panel-C9jYZ6pA.js → dependency-graph-panel-Vd-OsVLa.js} +4 -4
- package/dist/assets/{dialog-DUEuLcT2.js → dialog-CF5DtF1E.js} +1 -1
- package/dist/assets/{dist-DOFFh6Ii.js → dist-Dg7UO_Vw.js} +1 -1
- package/dist/assets/{documentation-panel-AsatrTfg.js → documentation-panel-xG2-zpwg.js} +1 -1
- package/dist/assets/{download-PR1bF3g_.js → download-B6EJS7Ar.js} +1 -1
- package/dist/assets/edit-page-7Hkti2j_.js +12 -0
- package/dist/assets/{error-banner-DU5Qb8a8.js → error-banner-DvT0IGDZ.js} +1 -1
- package/dist/assets/{error-panel-D_wVKV6I.js → error-panel-BxBpZYvt.js} +1 -1
- package/dist/assets/{es-CEE_7T0w.js → es-BoHEdemq.js} +1 -1
- package/dist/assets/{field-DDKGFzpC.js → field-Clr_fqUr.js} +1 -1
- package/dist/assets/{file-explorer-panel-DltK8JVp.js → file-explorer-panel-C9K0vIPl.js} +1 -1
- package/dist/assets/{floating-outline-BfdazXWm.js → floating-outline-DCrTuu2G.js} +1 -1
- package/dist/assets/{focus-CtlWIiQr.js → focus-DM53w5BH.js} +1 -1
- package/dist/assets/{form-Cy5TkLzh.js → form-BcKfhfZc.js} +2 -2
- package/dist/assets/{glide-data-editor-D_bRnWfy.js → glide-data-editor-CRb9AiCG.js} +1 -1
- package/dist/assets/{globals-BSLm1nlz.js → globals-Bf30kOQF.js} +1 -1
- package/dist/assets/{home-page-CEnaUutq.js → home-page-BRyNf7fl.js} +2 -2
- package/dist/assets/index-CBMqMxiq.js +43 -0
- package/dist/assets/index-DDc_1b-N.css +2 -0
- package/dist/assets/input-B80Yt1uu.js +1 -0
- package/dist/assets/{kiosk-mode-BnTZR6mM.js → kiosk-mode-P-NYHJID.js} +1 -1
- package/dist/assets/{label-qwandMoh.js → label-CNZLffHW.js} +1 -1
- package/dist/assets/{layout-BTiWDrbh.js → layout-DT91GUei.js} +4 -4
- package/dist/assets/links-D529u6GQ.js +1 -0
- package/dist/assets/{logs-panel-Cnp9tO_1.js → logs-panel-C2dfrRig.js} +1 -1
- package/dist/assets/{markdown-renderer-BSrbBHwX.js → markdown-renderer-BPnVa0ym.js} +2 -2
- package/dist/assets/{mermaid-BPkO79lo.js → mermaid--ZwxKP7u.js} +1 -1
- package/dist/assets/mode-Dq8MKjNR.js +1 -0
- package/dist/assets/{multi-map-fjX9ImVF.js → multi-map-CQd4MZr5.js} +1 -1
- package/dist/assets/name-cell-input-BaEPC7ON.js +1 -0
- package/dist/assets/{outline-panel-DCfj1bI-.js → outline-panel-Cca864H0.js} +1 -1
- package/dist/assets/{packages-panel-BiEckVdM.js → packages-panel-Cy_KAYmq.js} +1 -1
- package/dist/assets/{panels-BXRys72u.js → panels-BzlLZfye.js} +1 -1
- package/dist/assets/{process-output-wGlHkL-Q.js → process-output-Dn1rOp26.js} +1 -1
- package/dist/assets/{readonly-python-code-xbh7G2Y2.js → readonly-python-code-CXeF74Iq.js} +1 -1
- package/dist/assets/{renderShortcut-D0Pei-OA.js → renderShortcut-eU5Hsfml.js} +1 -1
- package/dist/assets/{run-page-BCwJRhCq.js → run-page-CM_n6pXD.js} +1 -1
- package/dist/assets/scratchpad-panel-XCkVY3Hp.js +1 -0
- package/dist/assets/{secrets-panel-CDWmmmBS.js → secrets-panel-BMY6PPth.js} +1 -1
- package/dist/assets/{select-D0g5GnIs.js → select-D9lTzMzP.js} +1 -1
- package/dist/assets/{session-panel-DuQl_oQp.js → session-panel-BDt6Y_mU.js} +1 -1
- package/dist/assets/{slides-component-MkPkpql1.js → slides-component-Dp0Yv5b0.js} +1 -1
- package/dist/assets/{snippets-panel-R_ql6HGu.js → snippets-panel-K-JKJQBf.js} +1 -1
- package/dist/assets/state-DWRZTH2y.js +1 -0
- package/dist/assets/state-JzO-Ni5T.js +1 -0
- package/dist/assets/{switch-CWzL-0WF.js → switch-RowEjq0T.js} +1 -1
- package/dist/assets/{terminal-BWM0fOMh.js → terminal-BhbNfCNw.js} +1 -1
- package/dist/assets/{textarea-CfvBt_Xm.js → textarea-Di1KKcL4.js} +1 -1
- package/dist/assets/{tracing-Kscqc1t3.js → tracing-nvbrZdpf.js} +1 -1
- package/dist/assets/{tracing-panel-BEzOflWc.js → tracing-panel-CTXJaO-A.js} +2 -2
- package/dist/assets/{types-DhuSHMNQ.js → types-CT2U5Ljy.js} +1 -1
- package/dist/assets/{useAddCell-C9lbOVO1.js → useAddCell-COb93CUl.js} +1 -1
- package/dist/assets/{useBoolean-B-A0dyIW.js → useBoolean-B_S7yTZz.js} +1 -1
- package/dist/assets/{useCellActionButton-fsh9MTAX.js → useCellActionButton-D5Zt1dDz.js} +1 -1
- package/dist/assets/{useDateFormatter-CV0QXb5P.js → useDateFormatter-DsANziQR.js} +1 -1
- package/dist/assets/useDeleteCell-DHF_xvAh.js +1 -0
- package/dist/assets/{useDependencyPanelTab-CngFbla0.js → useDependencyPanelTab-D59iW_MD.js} +1 -1
- package/dist/assets/useInterval-BGPIviJp.js +1 -0
- package/dist/assets/{useNotebookActions-D01w160c.js → useNotebookActions-DEl-rH-3.js} +1 -1
- package/dist/assets/{useNumberFormatter-D8ks3oPN.js → useNumberFormatter-FoXhpyAb.js} +1 -1
- package/dist/assets/usePress-DTwIUo40.js +7 -0
- package/dist/assets/useRunCells-CKEmgeKM.js +1 -0
- package/dist/assets/useSplitCell-D9YiO-z5.js +1 -0
- package/dist/assets/{useTheme-DfP1CWaW.js → useTheme-CNj0G_ol.js} +1 -1
- package/dist/assets/utilities.esm-DG4qccZc.js +3 -0
- package/dist/assets/utils-pfqq9IdB.js +1 -0
- package/dist/assets/{vega-component-B8ghmMYW.js → vega-component-C1voDf5W.js} +1 -1
- package/dist/assets/{write-secret-modal-CLm48gMe.js → write-secret-modal-hOetwavI.js} +1 -1
- package/dist/index.html +56 -56
- package/package.json +5 -5
- package/src/__mocks__/requests.ts +1 -0
- package/src/__tests__/mount.test.ts +128 -0
- package/src/components/app-config/__tests__/get-dirty-values.test.ts +1 -1
- package/src/components/app-config/ai-config.tsx +328 -28
- package/src/components/app-config/user-config-form.tsx +10 -3
- package/src/components/chat/acp/agent-panel.tsx +56 -43
- package/src/components/chat/chat-utils.ts +0 -19
- package/src/components/data-table/column-header.tsx +1 -1
- package/src/components/editor/KernelStartupErrorModal.tsx +2 -2
- package/src/components/editor/actions/name-cell-input.tsx +10 -4
- package/src/components/editor/ai/completion-handlers.tsx +1 -1
- package/src/components/editor/alerts/connecting-alert.tsx +33 -6
- package/src/components/editor/chrome/types.ts +2 -4
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +55 -58
- package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +150 -96
- package/src/components/editor/renderers/vertical-layout/__tests__/useFocusFirstEditor.test.ts +27 -0
- package/src/components/editor/renderers/vertical-layout/useFocusFirstEditor.ts +6 -0
- package/src/components/utils/lazy-mount.tsx +29 -8
- package/src/core/ai/ids/ids.ts +12 -4
- package/src/core/cells/cells.ts +2 -0
- package/src/core/cells/scrollCellIntoView.ts +3 -2
- package/src/core/codemirror/cm.ts +2 -0
- package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +123 -0
- package/src/core/codemirror/lsp/notebook-lsp.ts +44 -4
- package/src/core/codemirror/misc/__tests__/string-braces.test.ts +200 -0
- package/src/core/codemirror/misc/string-braces.ts +37 -0
- package/src/core/config/__tests__/config-schema.test.ts +36 -0
- package/src/core/config/config-schema.ts +1 -0
- package/src/core/export/__tests__/hooks.test.ts +504 -0
- package/src/core/export/hooks.ts +93 -4
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/kernel/__tests__/handlers.test.ts +2 -2
- package/src/core/kernel/state.ts +1 -0
- package/src/core/network/__tests__/requests-lazy.test.ts +1 -1
- package/src/core/network/__tests__/requests-network.test.ts +0 -18
- package/src/core/network/requests-lazy.ts +3 -2
- package/src/core/network/requests-network.ts +10 -7
- package/src/core/network/requests-static.ts +1 -0
- package/src/core/network/requests-toasting.tsx +1 -0
- package/src/core/network/types.ts +2 -0
- package/src/core/wasm/bridge.ts +1 -0
- package/src/css/globals.css +2 -0
- package/src/hooks/__tests__/useInterval.test.tsx +104 -0
- package/src/hooks/useInterval.ts +32 -6
- package/src/mount.tsx +6 -0
- package/src/plugins/impl/chat/ChatPlugin.tsx +2 -4
- package/src/plugins/impl/chat/chat-ui.tsx +62 -191
- package/src/plugins/impl/chat/types.ts +5 -12
- package/src/plugins/impl/data-frames/DataFramePlugin.tsx +3 -1
- package/src/utils/events.ts +1 -0
- package/dist/assets/VisuallyHidden-BodIky8L.js +0 -1
- package/dist/assets/agent-panel-Bm-vW8YL.js +0 -287
- package/dist/assets/button-DuYGqRtX.js +0 -1
- package/dist/assets/cell-editor-Cdtc1m3g.js +0 -23
- package/dist/assets/cell-link-CFAPzUg5.js +0 -1
- package/dist/assets/client-DfkWorYM.js +0 -4
- package/dist/assets/common-jorbwXZC.js +0 -1
- package/dist/assets/config-Ba3eeYri.js +0 -1
- package/dist/assets/context-BAYdLMF_.js +0 -1
- package/dist/assets/edit-page-B1Ed6RKp.js +0 -12
- package/dist/assets/index-DNg7_e7t.js +0 -43
- package/dist/assets/index-__6MNWbe.css +0 -2
- package/dist/assets/input-CaEtLL8p.js +0 -1
- package/dist/assets/links-Bpd4gqTj.js +0 -1
- package/dist/assets/mode-yhfN-4ye.js +0 -1
- package/dist/assets/name-cell-input-CmuWqgFR.js +0 -1
- package/dist/assets/scratchpad-panel-C6PpCYtK.js +0 -1
- package/dist/assets/state-DEHWsmkM.js +0 -1
- package/dist/assets/state-DXAf-ejz.js +0 -1
- package/dist/assets/useDeleteCell-ByImoTpm.js +0 -1
- package/dist/assets/useInterval-DpipYmgs.js +0 -1
- package/dist/assets/usePress-C2LPFxyv.js +0 -7
- package/dist/assets/useRunCells-CmnSPQtM.js +0 -1
- package/dist/assets/useSplitCell-BTH64tve.js +0 -1
- package/dist/assets/utilities.esm-CMQs6YPp.js +0 -3
- package/dist/assets/utils-CJJIceVn.js +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Copyright
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { NoKernelConnectedError } from "@/utils/errors";
|
|
4
4
|
import { Logger } from "@/utils/Logger";
|
|
@@ -67,6 +67,7 @@ const ACTIONS: Record<keyof AllRequests, Action> = {
|
|
|
67
67
|
autoExportAsHTML: "waitForConnectionOpen",
|
|
68
68
|
autoExportAsMarkdown: "waitForConnectionOpen",
|
|
69
69
|
autoExportAsIPYNB: "waitForConnectionOpen",
|
|
70
|
+
updateCellOutputs: "waitForConnectionOpen",
|
|
70
71
|
|
|
71
72
|
// Sidebar operations that wait for connection
|
|
72
73
|
listSecretKeys: "throwError",
|
|
@@ -152,7 +153,7 @@ export function createLazyRequests(
|
|
|
152
153
|
`Dropping request: ${key}, since not connected to a kernel.`,
|
|
153
154
|
);
|
|
154
155
|
// Silently drop the request
|
|
155
|
-
return
|
|
156
|
+
return;
|
|
156
157
|
|
|
157
158
|
case "throwError":
|
|
158
159
|
throw new NoKernelConnectedError();
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { once } from "lodash-es";
|
|
4
4
|
import { getRuntimeManager } from "../runtime/config";
|
|
5
|
-
import { store } from "../state/jotai";
|
|
6
5
|
import { API, createClientWithRuntimeManager } from "./api";
|
|
7
|
-
import {
|
|
6
|
+
import { waitForConnectionOpen } from "./connection";
|
|
8
7
|
import type { EditRequests, RunRequests } from "./types";
|
|
9
8
|
|
|
10
9
|
const { handleResponse, handleResponseReturnNull } = API;
|
|
@@ -105,11 +104,7 @@ export function createNetworkRequests(): EditRequests & RunRequests {
|
|
|
105
104
|
.then(handleResponseReturnNull);
|
|
106
105
|
},
|
|
107
106
|
sendRun: async (request) => {
|
|
108
|
-
|
|
109
|
-
// Otherwise we can get into a weird state of sending requests for cells that no longer exist.
|
|
110
|
-
if (!store.get(isConnectedAtom)) {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
107
|
+
await waitForConnectionOpen();
|
|
113
108
|
return getClient()
|
|
114
109
|
.POST("/api/kernel/run", {
|
|
115
110
|
body: request,
|
|
@@ -410,6 +405,14 @@ export function createNetworkRequests(): EditRequests & RunRequests {
|
|
|
410
405
|
})
|
|
411
406
|
.then(handleResponseReturnNull);
|
|
412
407
|
},
|
|
408
|
+
updateCellOutputs: async (request) => {
|
|
409
|
+
return getClient()
|
|
410
|
+
.POST("/api/export/update_cell_outputs", {
|
|
411
|
+
body: request,
|
|
412
|
+
params: getParams(),
|
|
413
|
+
})
|
|
414
|
+
.then(handleResponseReturnNull);
|
|
415
|
+
},
|
|
413
416
|
addPackage: (request) => {
|
|
414
417
|
return getClient()
|
|
415
418
|
.POST("/api/packages/add", {
|
|
@@ -79,6 +79,7 @@ export function createStaticRequests(): EditRequests & RunRequests {
|
|
|
79
79
|
autoExportAsHTML: throwNotInEditMode,
|
|
80
80
|
autoExportAsMarkdown: throwNotInEditMode,
|
|
81
81
|
autoExportAsIPYNB: throwNotInEditMode,
|
|
82
|
+
updateCellOutputs: throwNotInEditMode,
|
|
82
83
|
addPackage: throwNotInEditMode,
|
|
83
84
|
removePackage: throwNotInEditMode,
|
|
84
85
|
getPackageList: throwNotInEditMode,
|
|
@@ -64,6 +64,7 @@ export function createErrorToastingRequests(
|
|
|
64
64
|
autoExportAsHTML: "", // No toast
|
|
65
65
|
autoExportAsMarkdown: "", // No toast
|
|
66
66
|
autoExportAsIPYNB: "", // No toast
|
|
67
|
+
updateCellOutputs: "", // No toast
|
|
67
68
|
addPackage: "Failed to add package",
|
|
68
69
|
removePackage: "Failed to remove package",
|
|
69
70
|
getPackageList: "Failed to get package list",
|
|
@@ -21,6 +21,7 @@ export type ExportAsHTMLRequest = schemas["ExportAsHTMLRequest"];
|
|
|
21
21
|
export type ExportAsMarkdownRequest = schemas["ExportAsMarkdownRequest"];
|
|
22
22
|
export type ExportAsIPYNBRequest = schemas["ExportAsIPYNBRequest"];
|
|
23
23
|
export type ExportAsScriptRequest = schemas["ExportAsScriptRequest"];
|
|
24
|
+
export type UpdateCellOutputsRequest = schemas["UpdateCellOutputsRequest"];
|
|
24
25
|
export type FileCreateRequest = schemas["FileCreateRequest"];
|
|
25
26
|
export type FileCreateResponse = schemas["FileCreateResponse"];
|
|
26
27
|
export type FileDeleteRequest = schemas["FileDeleteRequest"];
|
|
@@ -175,6 +176,7 @@ export interface EditRequests {
|
|
|
175
176
|
autoExportAsHTML: (request: ExportAsHTMLRequest) => Promise<null>;
|
|
176
177
|
autoExportAsMarkdown: (request: ExportAsMarkdownRequest) => Promise<null>;
|
|
177
178
|
autoExportAsIPYNB: (request: ExportAsIPYNBRequest) => Promise<null>;
|
|
179
|
+
updateCellOutputs: (request: UpdateCellOutputsRequest) => Promise<null>;
|
|
178
180
|
// Package requests
|
|
179
181
|
getPackageList: () => Promise<ListPackagesResponse>;
|
|
180
182
|
getDependencyTree: () => Promise<DependencyTreeResponse>;
|
package/src/core/wasm/bridge.ts
CHANGED
|
@@ -589,6 +589,7 @@ export class PyodideBridge implements RunRequests, EditRequests {
|
|
|
589
589
|
autoExportAsHTML = throwNotImplemented;
|
|
590
590
|
autoExportAsMarkdown = throwNotImplemented;
|
|
591
591
|
autoExportAsIPYNB = throwNotImplemented;
|
|
592
|
+
updateCellOutputs = throwNotImplemented;
|
|
592
593
|
writeSecret = throwNotImplemented;
|
|
593
594
|
invokeAiTool = throwNotImplemented;
|
|
594
595
|
clearCache = throwNotImplemented;
|
package/src/css/globals.css
CHANGED
|
@@ -184,8 +184,10 @@
|
|
|
184
184
|
--shadow-2xl-solid: 10px 12px 0px 0px var(--base-shadow-darker), 0 0px 8px 0px hsl(0deg 0% 90% / 50%);
|
|
185
185
|
|
|
186
186
|
/* Solid shadows with lighter shade color */
|
|
187
|
+
|
|
187
188
|
/* biome-ignore format: definition needs to be oneline or breaks variants */
|
|
188
189
|
--shadow-sm-solid-shade: 2px 2px 0px 0px var(--base-shadow), 0px 0px 2px 0px hsl(0deg 0% 50% / 20%);
|
|
190
|
+
|
|
189
191
|
/* biome-ignore format: definition needs to be oneline or breaks variants */
|
|
190
192
|
--shadow-md-solid-shade: 4px 4px 0px 0px var(--base-shadow), 0 0px 2px 0px hsl(0deg 0% 60% / 50%);
|
|
191
193
|
}
|
|
@@ -67,4 +67,108 @@ describe("useInterval", () => {
|
|
|
67
67
|
vi.advanceTimersByTime(1000);
|
|
68
68
|
expect(callback).not.toHaveBeenCalled();
|
|
69
69
|
});
|
|
70
|
+
|
|
71
|
+
describe("skipIfRunning", () => {
|
|
72
|
+
it("should allow overlapping async calls by default", async () => {
|
|
73
|
+
let concurrentCalls = 0;
|
|
74
|
+
let maxConcurrentCalls = 0;
|
|
75
|
+
|
|
76
|
+
const callback = vi.fn(async () => {
|
|
77
|
+
concurrentCalls++;
|
|
78
|
+
maxConcurrentCalls = Math.max(maxConcurrentCalls, concurrentCalls);
|
|
79
|
+
// Simulate slow async work
|
|
80
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
81
|
+
concurrentCalls--;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
renderHook(() =>
|
|
85
|
+
useInterval(callback, { delayMs: 500, whenVisible: false }),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// First call at 500ms
|
|
89
|
+
vi.advanceTimersByTime(500);
|
|
90
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
91
|
+
|
|
92
|
+
// Second call at 1000ms (while first is still running)
|
|
93
|
+
vi.advanceTimersByTime(500);
|
|
94
|
+
expect(callback).toHaveBeenCalledTimes(2);
|
|
95
|
+
|
|
96
|
+
// Third call at 1500ms
|
|
97
|
+
vi.advanceTimersByTime(500);
|
|
98
|
+
expect(callback).toHaveBeenCalledTimes(3);
|
|
99
|
+
|
|
100
|
+
// Multiple concurrent calls should have occurred
|
|
101
|
+
expect(maxConcurrentCalls).toBeGreaterThan(1);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should skip calls when skipIfRunning is true", async () => {
|
|
105
|
+
let concurrentCalls = 0;
|
|
106
|
+
let maxConcurrentCalls = 0;
|
|
107
|
+
|
|
108
|
+
const callback = vi.fn(async () => {
|
|
109
|
+
concurrentCalls++;
|
|
110
|
+
maxConcurrentCalls = Math.max(maxConcurrentCalls, concurrentCalls);
|
|
111
|
+
// Simulate slow async work (3 seconds)
|
|
112
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 3000));
|
|
113
|
+
concurrentCalls--;
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
renderHook(() =>
|
|
117
|
+
useInterval(callback, {
|
|
118
|
+
delayMs: 500,
|
|
119
|
+
whenVisible: false,
|
|
120
|
+
skipIfRunning: true,
|
|
121
|
+
}),
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// First call at 500ms
|
|
125
|
+
await vi.advanceTimersByTimeAsync(500);
|
|
126
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
127
|
+
|
|
128
|
+
// Second interval tick at 1000ms - should be skipped since first is still running
|
|
129
|
+
await vi.advanceTimersByTimeAsync(500);
|
|
130
|
+
expect(callback).toHaveBeenCalledTimes(1); // Still 1, not 2
|
|
131
|
+
|
|
132
|
+
// Third interval tick at 1500ms - should still be skipped
|
|
133
|
+
await vi.advanceTimersByTimeAsync(500);
|
|
134
|
+
expect(callback).toHaveBeenCalledTimes(1); // Still 1
|
|
135
|
+
|
|
136
|
+
// Only one concurrent call should have occurred
|
|
137
|
+
expect(maxConcurrentCalls).toBe(1);
|
|
138
|
+
|
|
139
|
+
// Advance past the 3 second timeout to complete first callback
|
|
140
|
+
await vi.advanceTimersByTimeAsync(2000);
|
|
141
|
+
|
|
142
|
+
// Next interval tick should now be able to run
|
|
143
|
+
await vi.advanceTimersByTimeAsync(500);
|
|
144
|
+
expect(callback).toHaveBeenCalledTimes(2);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should allow next call after previous async call completes with skipIfRunning true", async () => {
|
|
148
|
+
const callback = vi.fn(async () => {
|
|
149
|
+
// Quick async operation
|
|
150
|
+
await Promise.resolve();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
renderHook(() =>
|
|
154
|
+
useInterval(callback, {
|
|
155
|
+
delayMs: 1000,
|
|
156
|
+
whenVisible: false,
|
|
157
|
+
skipIfRunning: true,
|
|
158
|
+
}),
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// First call
|
|
162
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
163
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
164
|
+
|
|
165
|
+
// Second call - should proceed since first completed
|
|
166
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
167
|
+
expect(callback).toHaveBeenCalledTimes(2);
|
|
168
|
+
|
|
169
|
+
// Third call
|
|
170
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
171
|
+
expect(callback).toHaveBeenCalledTimes(3);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
70
174
|
});
|
package/src/hooks/useInterval.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
-
import { useEffect, useRef } from "react";
|
|
2
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
3
3
|
import { useEventListener } from "./useEventListener";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Creates an interval that runs a callback every `delayMs` milliseconds.
|
|
7
|
+
*
|
|
8
|
+
* @param callback - The callback to run.
|
|
9
|
+
* @param opts - The options for the interval.
|
|
10
|
+
* @param opts.delayMs - The delay in milliseconds between runs.
|
|
11
|
+
* @param opts.whenVisible - Whether to run the callback when the document is visible.
|
|
12
|
+
* @param opts.disabled - Whether to disable the interval.
|
|
13
|
+
* @param opts.skipIfRunning - Whether to skip the callback if it is already running.
|
|
7
14
|
*/
|
|
8
15
|
export function useInterval(
|
|
9
16
|
callback: () => void,
|
|
@@ -11,16 +18,35 @@ export function useInterval(
|
|
|
11
18
|
delayMs: number | null;
|
|
12
19
|
whenVisible: boolean;
|
|
13
20
|
disabled?: boolean;
|
|
21
|
+
skipIfRunning?: boolean;
|
|
14
22
|
},
|
|
15
23
|
) {
|
|
16
|
-
const {
|
|
17
|
-
|
|
24
|
+
const {
|
|
25
|
+
delayMs,
|
|
26
|
+
whenVisible,
|
|
27
|
+
disabled = false,
|
|
28
|
+
skipIfRunning = false,
|
|
29
|
+
} = opts;
|
|
30
|
+
const savedCallback = useRef<() => void | Promise<void>>(undefined);
|
|
31
|
+
const isRunning = useRef(false);
|
|
18
32
|
|
|
19
33
|
// Store the callback
|
|
20
34
|
useEffect(() => {
|
|
21
35
|
savedCallback.current = callback;
|
|
22
36
|
}, [callback]);
|
|
23
37
|
|
|
38
|
+
const runCallback = useCallback(async () => {
|
|
39
|
+
if (isRunning.current && skipIfRunning) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
isRunning.current = true;
|
|
43
|
+
try {
|
|
44
|
+
await savedCallback.current?.();
|
|
45
|
+
} finally {
|
|
46
|
+
isRunning.current = false;
|
|
47
|
+
}
|
|
48
|
+
}, [skipIfRunning]);
|
|
49
|
+
|
|
24
50
|
// Run the interval
|
|
25
51
|
useEffect(() => {
|
|
26
52
|
if (delayMs === null || disabled) {
|
|
@@ -32,16 +58,16 @@ export function useInterval(
|
|
|
32
58
|
return;
|
|
33
59
|
}
|
|
34
60
|
|
|
35
|
-
|
|
61
|
+
runCallback();
|
|
36
62
|
}, delayMs);
|
|
37
63
|
|
|
38
64
|
return () => clearInterval(id);
|
|
39
|
-
}, [delayMs, whenVisible, disabled]);
|
|
65
|
+
}, [delayMs, whenVisible, disabled, runCallback]);
|
|
40
66
|
|
|
41
67
|
// When the document becomes visible, run the callback
|
|
42
68
|
useEventListener(document, "visibilitychange", () => {
|
|
43
69
|
if (document.visibilityState === "visible" && whenVisible && !disabled) {
|
|
44
|
-
|
|
70
|
+
runCallback();
|
|
45
71
|
}
|
|
46
72
|
});
|
|
47
73
|
|
package/src/mount.tsx
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
import { MarimoApp, preloadPage } from "./core/MarimoApp";
|
|
30
30
|
import { type AppMode, initialModeAtom, viewStateAtom } from "./core/mode";
|
|
31
31
|
import { cleanupAuthQueryParams } from "./core/network/auth";
|
|
32
|
+
import { connectionAtom } from "./core/network/connection";
|
|
32
33
|
import { requestClientAtom } from "./core/network/requests";
|
|
33
34
|
import { resolveRequestClient } from "./core/network/resolve";
|
|
34
35
|
import {
|
|
@@ -42,6 +43,7 @@ import { isStaticNotebook } from "./core/static/static-state";
|
|
|
42
43
|
import { maybeRegisterVSCodeBindings } from "./core/vscode/vscode-bindings";
|
|
43
44
|
import type { FileStore } from "./core/wasm/store";
|
|
44
45
|
import { notebookFileStore } from "./core/wasm/store";
|
|
46
|
+
import { WebSocketState } from "./core/websocket/types";
|
|
45
47
|
import { vegaLoader } from "./plugins/impl/vega/loader";
|
|
46
48
|
import { initializePlugins } from "./plugins/plugins";
|
|
47
49
|
import { ThemeProvider } from "./theme/ThemeProvider";
|
|
@@ -304,6 +306,10 @@ function initStore(options: unknown) {
|
|
|
304
306
|
...firstRuntimeConfig,
|
|
305
307
|
serverToken: parsedOptions.data.serverToken,
|
|
306
308
|
});
|
|
309
|
+
// If the remote runtime is not lazy, start it in CONNECTING
|
|
310
|
+
if (!firstRuntimeConfig.lazy && !isStaticNotebook()) {
|
|
311
|
+
store.set(connectionAtom, { state: WebSocketState.CONNECTING });
|
|
312
|
+
}
|
|
307
313
|
} else {
|
|
308
314
|
store.set(runtimeConfigAtom, {
|
|
309
315
|
...DEFAULT_RUNTIME_CONFIG,
|
|
@@ -15,7 +15,7 @@ export type PluginFunctions = {
|
|
|
15
15
|
get_chat_history: (req: {}) => Promise<{ messages: UIMessage[] }>;
|
|
16
16
|
delete_chat_history: (req: {}) => Promise<null>;
|
|
17
17
|
delete_chat_message: (req: { index: number }) => Promise<null>;
|
|
18
|
-
send_prompt: (req: SendMessageRequest) => Promise<
|
|
18
|
+
send_prompt: (req: SendMessageRequest) => Promise<unknown>;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
const messageSchema = z.array(
|
|
@@ -47,7 +47,6 @@ export const ChatPlugin = createPlugin<{ messages: UIMessage[] }>(
|
|
|
47
47
|
maxHeight: z.number().optional(),
|
|
48
48
|
config: configSchema,
|
|
49
49
|
allowAttachments: z.union([z.boolean(), z.string().array()]),
|
|
50
|
-
frontendManaged: z.boolean(),
|
|
51
50
|
}),
|
|
52
51
|
)
|
|
53
52
|
.withFunctions<PluginFunctions>({
|
|
@@ -67,7 +66,7 @@ export const ChatPlugin = createPlugin<{ messages: UIMessage[] }>(
|
|
|
67
66
|
config: configSchema,
|
|
68
67
|
}),
|
|
69
68
|
)
|
|
70
|
-
.output(z.
|
|
69
|
+
.output(z.unknown()),
|
|
71
70
|
})
|
|
72
71
|
.renderer((props) => (
|
|
73
72
|
<TooltipProvider>
|
|
@@ -77,7 +76,6 @@ export const ChatPlugin = createPlugin<{ messages: UIMessage[] }>(
|
|
|
77
76
|
showConfigurationControls={props.data.showConfigurationControls}
|
|
78
77
|
maxHeight={props.data.maxHeight}
|
|
79
78
|
allowAttachments={props.data.allowAttachments}
|
|
80
|
-
frontendManaged={props.data.frontendManaged}
|
|
81
79
|
config={props.data.config}
|
|
82
80
|
get_chat_history={props.functions.get_chat_history}
|
|
83
81
|
delete_chat_history={props.functions.delete_chat_history}
|