@marimo-team/islands 0.25.3-dev308 → 0.25.3-dev310
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-DJnSxHJB.js → ConnectedDataExplorerComponent-DieU6L4x.js} +1 -1
- package/dist/{ErrorBoundary-flfhLFje.js → ErrorBoundary-DjoeT4kv.js} +1 -1
- package/dist/{abnfDiagram-VCTEODGH-CR0U5hup.js → abnfDiagram-VCTEODGH-LOVrjbdp.js} +2 -2
- package/dist/{any-language-editor-C5dsTpeB.js → any-language-editor-DMC2JE9_.js} +1 -1
- package/dist/{architectureDiagram-5GKGNRK7-Bc_fbI1Q.js → architectureDiagram-5GKGNRK7-RZsm312h.js} +1 -1
- package/dist/assets/__vite-browser-external-BSp9tJ_k.js +1 -0
- package/dist/assets/worker-BdfPA0g5.js +171 -0
- package/dist/{chat-ui-C4iLRzY0.js → chat-ui-DkuxGEDp.js} +418 -418
- package/dist/{chunk-IMKFNOWR-y9z5oeAY.js → chunk-IMKFNOWR-B6agbLjo.js} +1 -1
- package/dist/{chunk-SHT3W25Y-Tu-V-e_v.js → chunk-SHT3W25Y-CfULafYG.js} +1 -1
- package/dist/{chunk-SVP7TREG-CJFxs5R_.js → chunk-SVP7TREG-DEOpPMJp.js} +1 -1
- package/dist/{chunk-TICWLB2K-zkTP7ya7.js → chunk-TICWLB2K-DaH-gkX5.js} +1 -1
- package/dist/{classDiagram-ZZMXUADV-DxJAZI0N.js → classDiagram-ZZMXUADV-C0CyiWQ7.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-TmHo2BpS.js → classDiagram-v2-VYDZK3BY-xfIc9e2Y.js} +2 -2
- package/dist/{code-visibility-BDs_r7jH.js → code-visibility-CduMVVFS.js} +32 -32
- package/dist/{common-CMTsL35V.js → common-CMcEcucH.js} +919 -919
- package/dist/{config-BG2qKzDg.js → config-BVYA1P0C.js} +103 -76
- package/dist/{cynefinDiagram-5FMLGOSQ-Eb40hmhD.js → cynefinDiagram-5FMLGOSQ-DwGYhKu0.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-_paaVwit.js → dagre-GXQ25YYZ-DfMaWJOV.js} +1 -1
- package/dist/{data-grid-overlay-editor-es6V6qf-.js → data-grid-overlay-editor-BYrIVut3.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-BtKJm1_4.js → diagram-S7CK7UJ4-Bw_L87GZ.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-DQbxhEMd.js → diagram-UQ7AKVKN-nsrs3Fbz.js} +1 -1
- package/dist/{diagram-VX7I27RA-DITDIfGC.js → diagram-VX7I27RA-7ST9lEqP.js} +1 -1
- package/dist/{diagram-Z3DM3KII-Ct9_zo6F.js → diagram-Z3DM3KII-BnGte-HX.js} +1 -1
- package/dist/{ebnfDiagram-PWID7BFC-6cOntwfv.js → ebnfDiagram-PWID7BFC-zaKyJ1YL.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-CJiOER41.js → erDiagram-RLTQ6QDP-Cx7g95FZ.js} +1 -1
- package/dist/{error-banner-DGOrmBk5.js → error-banner-DkQNU4bn.js} +128 -128
- package/dist/{error-entries-BWmleiL0.js → error-entries-istW7Xbs.js} +589 -544
- package/dist/{feedback-button-D4eVjeNs.js → feedback-button-Zvm26aOQ.js} +107 -106
- package/dist/{flowDiagram-HODETNUW-1lPBEUuW.js → flowDiagram-HODETNUW-C7nIqaHP.js} +2 -2
- package/dist/{formats-qhBmYb0l.js → formats-CMa7L3E8.js} +1 -1
- package/dist/{glide-data-editor-ahfaPdFW.js → glide-data-editor-BE-GgVp4.js} +3 -3
- package/dist/{globals-m8oswz--.js → globals-Dbru-WLP.js} +1 -1
- package/dist/{html-to-image-B8hE6HIH.js → html-to-image-pPbR2cDx.js} +4512 -4464
- package/dist/{infoDiagram-27XIBGKW-C6K4tri5.js → infoDiagram-27XIBGKW-BTM_NXsn.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-DwcwgRNH.js → ishikawaDiagram-5VMMS53U-K0FZrlvv.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-E489OMdc.js → kanban-definition-UXKFOSKX-ftznOyrz.js} +1 -1
- package/dist/main.js +6595 -6186
- package/dist/{mermaid-DMPMkVqY.js → mermaid-CNgH43ED.js} +33 -33
- package/dist/{mindmap-definition-YA3MSWOX-DWHjasOp.js → mindmap-definition-YA3MSWOX-CmzbxKfM.js} +1 -1
- package/dist/{number-overlay-editor-C6klKzEr.js → number-overlay-editor-qvlPZ-l-.js} +1 -1
- package/dist/{pegDiagram-XKGWAZYB-COjcPOZT.js → pegDiagram-XKGWAZYB-DfTAWfGE.js} +2 -2
- package/dist/{pieDiagram-E7YTZNPT-BHAVZ38h.js → pieDiagram-E7YTZNPT-_kGQstZj.js} +1 -1
- package/dist/{process-output-DZDL_j1a.js → process-output-BySO1nIs.js} +3 -3
- package/dist/{railroadDiagram-O6MQD6OU-94ThFyKX.js → railroadDiagram-O6MQD6OU-D41WmIff.js} +2 -2
- package/dist/{requirementDiagram-BXWQKSXE-CpF-Fxbb.js → requirementDiagram-BXWQKSXE-Bh53ZI3I.js} +1 -1
- package/dist/{reveal-component-DwnxPyVz.js → reveal-component-DQaegdcW.js} +4 -4
- package/dist/{sequenceDiagram-WJ2MYXX4-CvANK2x7.js → sequenceDiagram-WJ2MYXX4-DBX27dNm.js} +1 -1
- package/dist/{stateDiagram-D77RDMKH-WHuJ4mJL.js → stateDiagram-D77RDMKH-CSYz2IZi.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-DbO7lwYq.js → stateDiagram-v2-MP3YSRHH-DVLvtOqn.js} +2 -2
- package/dist/{swimlanes-42K2YHIH-CS2s29Vo.js → swimlanes-42K2YHIH-D59N0ktA.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-CXaA1vQg.js → swimlanesDiagram-VR7AAH4N-CRgpmVSW.js} +2 -2
- package/dist/{timeline-definition-24CTP7MA-B-SQMg5h.js → timeline-definition-24CTP7MA-C_xIIZDM.js} +1 -1
- package/dist/toDate-5qPtZ6HL.js +94 -0
- package/dist/{vega-component-DwqGfN3_.js → vega-component-Bly2lDhz.js} +10 -10
- package/dist/{vennDiagram-4TSXK5OY-etEFgvaG.js → vennDiagram-4TSXK5OY-DlOf0Mjs.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-BEQN7g62.js → wardleyDiagram-VM6X3IG4-zD_2guVe.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-D8-e5Z0R.js → xychartDiagram-S5SC5T6Z-CA0Dy1ZM.js} +1 -1
- package/package.json +1 -1
- package/src/__mocks__/executions.ts +35 -13
- package/src/__mocks__/requests.ts +0 -1
- package/src/components/editor/cell/__tests__/useRunCells.test.tsx +86 -101
- package/src/components/editor/cell/useRunCells.ts +38 -65
- package/src/components/editor/notebook-cell.tsx +32 -20
- package/src/components/editor/output/ansi-reduce.ts +7 -0
- package/src/core/ai/__tests__/staged-cells.test.ts +1 -1
- package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +2 -0
- package/src/core/cells/__tests__/cells.test.ts +0 -14
- package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +15 -0
- package/src/core/cells/__tests__/utils.test.ts +1 -1
- package/src/core/cells/add-missing-import.ts +2 -5
- package/src/core/cells/cell.ts +1 -0
- package/src/core/cells/cells.ts +1 -2
- package/src/core/executions/__tests__/atoms.test.ts +227 -43
- package/src/core/executions/__tests__/hooks.test.tsx +1 -2
- package/src/core/executions/__tests__/input.test.ts +86 -0
- package/src/core/executions/__tests__/run.test.ts +390 -0
- package/src/core/executions/__tests__/select.test.ts +105 -30
- package/src/core/executions/atoms.ts +186 -71
- package/src/core/executions/index.ts +7 -6
- package/src/core/executions/input.ts +55 -0
- package/src/core/executions/run.ts +120 -0
- package/src/core/executions/select.ts +59 -24
- package/src/core/islands/__tests__/bridge.test.ts +35 -54
- package/src/core/islands/bootstrap.ts +5 -1
- package/src/core/islands/bridge.ts +16 -28
- package/src/core/islands/components/IslandControls.tsx +15 -7
- package/src/core/islands/components/__tests__/IslandControls.test.tsx +36 -53
- package/src/core/islands/worker/worker.tsx +45 -19
- package/src/core/network/__tests__/requests-lazy.test.ts +14 -24
- package/src/core/network/__tests__/requests-toasting.test.tsx +7 -7
- package/src/core/network/requests-lazy.ts +2 -3
- package/src/core/network/requests-network.ts +0 -9
- package/src/core/network/requests-static.ts +0 -1
- package/src/core/network/requests-toasting.tsx +0 -1
- package/src/core/network/types.ts +0 -2
- package/src/core/notebook/replica/__tests__/fixtures.ts +17 -1
- package/src/core/notebook/replica/backend.ts +3 -1
- package/src/core/runtime/__tests__/runtime.test.ts +36 -0
- package/src/core/runtime/config.ts +36 -1
- package/src/core/runtime/runtime.ts +21 -1
- package/src/core/transports/static.ts +4 -3
- package/src/core/wasm/bridge.ts +0 -17
- package/dist/assets/__vite-browser-external-DzTYIHH_.js +0 -1
- package/dist/assets/worker-BH08I2Il.js +0 -171
- package/dist/static-state-Dtocd9UO.js +0 -68
|
@@ -371,6 +371,8 @@ export class StatefulOutputMessage implements OutputMessage {
|
|
|
371
371
|
public readonly timestamp: OutputMessage["timestamp"];
|
|
372
372
|
public readonly code: OutputMessage["code"];
|
|
373
373
|
public readonly id: StringOutputMessage["id"];
|
|
374
|
+
/** The prompt this entry is, when it asks for input. A reply names it. */
|
|
375
|
+
public readonly input_id: OutputMessage["input_id"];
|
|
374
376
|
private ansiReducer = new AnsiReducer();
|
|
375
377
|
private _data: string;
|
|
376
378
|
|
|
@@ -388,6 +390,7 @@ export class StatefulOutputMessage implements OutputMessage {
|
|
|
388
390
|
ansiReducer,
|
|
389
391
|
code: message.code,
|
|
390
392
|
id: message.id,
|
|
393
|
+
input_id: message.input_id,
|
|
391
394
|
});
|
|
392
395
|
}
|
|
393
396
|
|
|
@@ -398,6 +401,7 @@ export class StatefulOutputMessage implements OutputMessage {
|
|
|
398
401
|
ansiReducer,
|
|
399
402
|
code,
|
|
400
403
|
id,
|
|
404
|
+
input_id,
|
|
401
405
|
}: {
|
|
402
406
|
mimetype: OutputMessage["mimetype"];
|
|
403
407
|
channel: OutputMessage["channel"];
|
|
@@ -405,12 +409,14 @@ export class StatefulOutputMessage implements OutputMessage {
|
|
|
405
409
|
ansiReducer: AnsiReducer;
|
|
406
410
|
code: OutputMessage["code"];
|
|
407
411
|
id: StringOutputMessage["id"];
|
|
412
|
+
input_id: OutputMessage["input_id"];
|
|
408
413
|
}) {
|
|
409
414
|
this.mimetype = mimetype;
|
|
410
415
|
this.channel = channel;
|
|
411
416
|
this.timestamp = timestamp;
|
|
412
417
|
this.code = code;
|
|
413
418
|
this.id = id;
|
|
419
|
+
this.input_id = input_id;
|
|
414
420
|
this.ansiReducer = ansiReducer;
|
|
415
421
|
this._data = this.ansiReducer.render();
|
|
416
422
|
}
|
|
@@ -424,6 +430,7 @@ export class StatefulOutputMessage implements OutputMessage {
|
|
|
424
430
|
ansiReducer: this.ansiReducer,
|
|
425
431
|
code: this.code,
|
|
426
432
|
id: this.id,
|
|
433
|
+
input_id: this.input_id,
|
|
427
434
|
});
|
|
428
435
|
}
|
|
429
436
|
|
|
@@ -47,7 +47,7 @@ vi.mock("@/components/editor/cell/useDeleteCell", () => ({
|
|
|
47
47
|
}));
|
|
48
48
|
|
|
49
49
|
vi.mock("@/core/network/requests", () => ({
|
|
50
|
-
getRequestClient: () => ({
|
|
50
|
+
getRequestClient: () => ({}),
|
|
51
51
|
}));
|
|
52
52
|
|
|
53
53
|
vi.mock("../../codemirror/language/cell-code", () => ({
|
|
@@ -92,6 +92,7 @@ exports[`cell reducer > can initialize stdin > second stdin requested 1`] = `
|
|
|
92
92
|
"channel": "stdin",
|
|
93
93
|
"data": "what is your name?",
|
|
94
94
|
"id": 2,
|
|
95
|
+
"input_id": undefined,
|
|
95
96
|
"mimetype": "text/plain",
|
|
96
97
|
"response": "Marimo!",
|
|
97
98
|
"timestamp": 2,
|
|
@@ -132,6 +133,7 @@ exports[`cell reducer > can initialize stdin > stdin answered 1`] = `
|
|
|
132
133
|
"channel": "stdin",
|
|
133
134
|
"data": "what is your name?",
|
|
134
135
|
"id": 2,
|
|
136
|
+
"input_id": undefined,
|
|
135
137
|
"mimetype": "text/plain",
|
|
136
138
|
"response": "Marimo!",
|
|
137
139
|
"timestamp": 2,
|
|
@@ -18,11 +18,6 @@ import {
|
|
|
18
18
|
vi,
|
|
19
19
|
} from "vitest";
|
|
20
20
|
import { MockExecutions } from "@/__mocks__/executions";
|
|
21
|
-
import {
|
|
22
|
-
notebookQueuedOrRunningCountAtom,
|
|
23
|
-
submitCells,
|
|
24
|
-
} from "@/core/executions";
|
|
25
|
-
import { store } from "@/core/state/jotai";
|
|
26
21
|
import { MockNotebook } from "@/__mocks__/notebook";
|
|
27
22
|
import { cellId } from "@/__tests__/branded";
|
|
28
23
|
import type { CellHandle } from "@/components/editor/notebook-cell";
|
|
@@ -1109,15 +1104,6 @@ describe("cell reducer", () => {
|
|
|
1109
1104
|
expect(handleAtom2).toBe(handleAtom1);
|
|
1110
1105
|
});
|
|
1111
1106
|
});
|
|
1112
|
-
it("withdraws the submission of a cell that leaves the document", () => {
|
|
1113
|
-
MockExecutions.seed(store, []);
|
|
1114
|
-
submitCells(store, [firstCellId]);
|
|
1115
|
-
expect(store.get(notebookQueuedOrRunningCountAtom)).toBe(1);
|
|
1116
|
-
|
|
1117
|
-
actions.reconcileDocument({ order: [], cells: new Map() });
|
|
1118
|
-
|
|
1119
|
-
expect(store.get(notebookQueuedOrRunningCountAtom)).toBe(0);
|
|
1120
|
-
});
|
|
1121
1107
|
});
|
|
1122
1108
|
|
|
1123
1109
|
describe("createTracebackInfoAtom", () => {
|
|
@@ -759,3 +759,18 @@ describe("collapseConsoleOutputs", () => {
|
|
|
759
759
|
});
|
|
760
760
|
});
|
|
761
761
|
});
|
|
762
|
+
|
|
763
|
+
it("keeps a prompt's input id when the entry becomes stateful", () => {
|
|
764
|
+
const prompt: OutputMessage = {
|
|
765
|
+
channel: "stdin",
|
|
766
|
+
mimetype: "text/plain",
|
|
767
|
+
data: "Name: ",
|
|
768
|
+
timestamp: 1,
|
|
769
|
+
input_id: "prompt-1",
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
const [collapsed] = collapseConsoleOutputs([prompt]);
|
|
773
|
+
|
|
774
|
+
expect(collapsed.input_id).toBe("prompt-1");
|
|
775
|
+
expect(collapsed.channel).toBe("stdin");
|
|
776
|
+
});
|
|
@@ -131,7 +131,7 @@ describe("staleCellIds with a run in progress", () => {
|
|
|
131
131
|
const state = notebook(cells);
|
|
132
132
|
const ran = MockExecutions.completed("cell1");
|
|
133
133
|
const executions = MockExecutions.inputs([ran], {
|
|
134
|
-
|
|
134
|
+
requested: new Set(["cell1"]),
|
|
135
135
|
});
|
|
136
136
|
|
|
137
137
|
expect(staleCellIds(state, executions, freshness(cells))).toEqual([]);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { runCells } from "../executions";
|
|
4
4
|
import { store } from "../state/jotai";
|
|
5
5
|
import { variablesAtom } from "../variables/state";
|
|
6
6
|
import { notebookAtom } from "./cells";
|
|
@@ -124,10 +124,7 @@ function addImport({
|
|
|
124
124
|
});
|
|
125
125
|
newCellId = created === null ? null : id;
|
|
126
126
|
if (autoInstantiate && newCellId !== null) {
|
|
127
|
-
void
|
|
128
|
-
cellIds: [newCellId],
|
|
129
|
-
codes: [importStatement],
|
|
130
|
-
});
|
|
127
|
+
void runCells(store, [newCellId]);
|
|
131
128
|
}
|
|
132
129
|
},
|
|
133
130
|
});
|
package/src/core/cells/cell.ts
CHANGED
package/src/core/cells/cells.ts
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
cellExecutionInputsAtom,
|
|
11
11
|
cellExecutionView,
|
|
12
12
|
cellFreshnessAtom,
|
|
13
|
-
withdrawCells,
|
|
14
13
|
} from "@/core/executions";
|
|
15
14
|
import type { CellColumnId } from "./layout";
|
|
16
15
|
import { invariant } from "@/utils/invariant";
|
|
@@ -218,6 +217,7 @@ const {
|
|
|
218
217
|
data: stdinOutput.data,
|
|
219
218
|
timestamp: stdinOutput.timestamp,
|
|
220
219
|
id: stdinOutput.id,
|
|
220
|
+
input_id: stdinOutput.input_id,
|
|
221
221
|
// An empty response resolves the prompt without retaining the secret.
|
|
222
222
|
response: stdinOutput.mimetype === "text/password" ? "" : response,
|
|
223
223
|
};
|
|
@@ -550,7 +550,6 @@ export function releaseCellAtoms(cellId: CellId) {
|
|
|
550
550
|
cellDataAtom.remove(cellId);
|
|
551
551
|
cellRuntimeAtom.remove(cellId);
|
|
552
552
|
cellHandleAtom.remove(cellId);
|
|
553
|
-
withdrawCells(store, [cellId]);
|
|
554
553
|
}
|
|
555
554
|
|
|
556
555
|
/** Subscribes to reactive updates of the cell's data. */
|
|
@@ -4,16 +4,49 @@ import { createStore } from "jotai";
|
|
|
4
4
|
import { describe, expect, it } from "vitest";
|
|
5
5
|
import { MockExecutions } from "@/__mocks__/executions";
|
|
6
6
|
import { cellId } from "@/__tests__/branded";
|
|
7
|
+
import type * as Protocol from "@marimo-team/client/protocol";
|
|
7
8
|
import type { CellMessage } from "@/core/kernel/messages";
|
|
8
9
|
import {
|
|
10
|
+
document as documentOf,
|
|
11
|
+
withCells,
|
|
12
|
+
} from "@/core/notebook/document/__tests__/fixtures";
|
|
13
|
+
import type { Executions } from "@/core/notebook/replica";
|
|
14
|
+
import {
|
|
15
|
+
acceptRun,
|
|
9
16
|
cellExecutionInputsAtom,
|
|
10
17
|
notebookQueuedOrRunningCountAtom,
|
|
11
18
|
observeCellMessage,
|
|
12
|
-
|
|
19
|
+
requestRun,
|
|
20
|
+
type RunRequest,
|
|
21
|
+
withdrawRequest,
|
|
13
22
|
} from "../atoms";
|
|
14
23
|
import { cellExecutionView } from "../select";
|
|
15
24
|
|
|
16
25
|
const C1 = cellId("c1");
|
|
26
|
+
const C2 = cellId("c2");
|
|
27
|
+
|
|
28
|
+
type Store = ReturnType<typeof createStore>;
|
|
29
|
+
|
|
30
|
+
function run(
|
|
31
|
+
cells: Protocol.CellId[],
|
|
32
|
+
status: Protocol.Run["status"] = "queued",
|
|
33
|
+
id = "run-1",
|
|
34
|
+
): Protocol.Run {
|
|
35
|
+
return {
|
|
36
|
+
id,
|
|
37
|
+
notebook_id: "n1",
|
|
38
|
+
runtime_id: "r1",
|
|
39
|
+
cells,
|
|
40
|
+
status,
|
|
41
|
+
outcome: status === "completed" ? "succeeded" : null,
|
|
42
|
+
started_at: null,
|
|
43
|
+
completed_at: null,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function withRuns(executions: Executions, runs: Protocol.Run[]): Executions {
|
|
48
|
+
return { ...executions, runs: new Map(runs.map((r) => [r.id, r])) };
|
|
49
|
+
}
|
|
17
50
|
|
|
18
51
|
function cellOp(status: CellMessage["status"]): CellMessage {
|
|
19
52
|
return {
|
|
@@ -25,14 +58,83 @@ function cellOp(status: CellMessage["status"]): CellMessage {
|
|
|
25
58
|
};
|
|
26
59
|
}
|
|
27
60
|
|
|
28
|
-
|
|
29
|
-
|
|
61
|
+
/** A store whose replica shows the two-cell fixture document. */
|
|
62
|
+
function shown() {
|
|
63
|
+
const store = createStore();
|
|
64
|
+
const replica = MockExecutions.seed(store, [], null, documentOf());
|
|
65
|
+
return { store, replica };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function request(store: Store, cells: Protocol.CellId[]): RunRequest {
|
|
69
|
+
const made = requestRun(store, cells);
|
|
70
|
+
if (made === null) {
|
|
71
|
+
throw new Error("no notebook is shown");
|
|
72
|
+
}
|
|
73
|
+
return made;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function statusOf(store: Store, id = C1) {
|
|
77
|
+
return cellExecutionView(store.get(cellExecutionInputsAtom), id).status;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function requested(store: Store) {
|
|
81
|
+
return [...store.get(cellExecutionInputsAtom).requested];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
describe("requestRun", () => {
|
|
85
|
+
it("reads the cells as queued until the request is withdrawn", () => {
|
|
86
|
+
const { store } = shown();
|
|
87
|
+
const made = request(store, [C1, C2]);
|
|
88
|
+
expect(statusOf(store, C1)).toBe("queued");
|
|
89
|
+
expect(statusOf(store, C2)).toBe("queued");
|
|
90
|
+
expect(store.get(notebookQueuedOrRunningCountAtom)).toBe(2);
|
|
91
|
+
|
|
92
|
+
withdrawRequest(store, made);
|
|
93
|
+
|
|
94
|
+
expect(statusOf(store, C1)).toBe("idle");
|
|
95
|
+
expect(store.get(notebookQueuedOrRunningCountAtom)).toBe(0);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("records nothing while no notebook is shown", () => {
|
|
30
99
|
const store = createStore();
|
|
31
|
-
MockExecutions.seed(store, []);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
).toBe("
|
|
100
|
+
const replica = MockExecutions.seed(store, [], null, documentOf());
|
|
101
|
+
store.set(replica.notebookAtom, null);
|
|
102
|
+
|
|
103
|
+
expect(requestRun(store, [C1])).toBeNull();
|
|
104
|
+
expect(statusOf(store)).toBe("idle");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("stops showing a requested cell that leaves the document", () => {
|
|
108
|
+
const { store, replica } = shown();
|
|
109
|
+
request(store, [C1]);
|
|
110
|
+
expect(store.get(notebookQueuedOrRunningCountAtom)).toBe(1);
|
|
111
|
+
|
|
112
|
+
store.set(replica.documentAtom, withCells(documentOf(), { c1: null }));
|
|
113
|
+
|
|
114
|
+
expect(store.get(notebookQueuedOrRunningCountAtom)).toBe(0);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("forgets requests and blocked cells with the notebook they were made in", () => {
|
|
118
|
+
const { store, replica } = shown();
|
|
119
|
+
request(store, [C1]);
|
|
120
|
+
observeCellMessage(store, {
|
|
121
|
+
...cellOp("disabled-transitively"),
|
|
122
|
+
cell_id: C2,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
store.set(replica.notebookAtom, MockExecutions.notebook("n2"));
|
|
126
|
+
|
|
127
|
+
expect(requested(store)).toEqual([]);
|
|
128
|
+
expect(statusOf(store)).toBe("idle");
|
|
129
|
+
expect(store.get(cellExecutionInputsAtom).blocked.size).toBe(0);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("observeCellMessage", () => {
|
|
134
|
+
it("does not show a requested cell as queued once the kernel reports it blocked", () => {
|
|
135
|
+
const { store } = shown();
|
|
136
|
+
request(store, [C1]);
|
|
137
|
+
expect(statusOf(store)).toBe("queued");
|
|
36
138
|
|
|
37
139
|
observeCellMessage(store, cellOp("disabled-transitively"));
|
|
38
140
|
|
|
@@ -42,13 +144,12 @@ describe("observeCellMessage", () => {
|
|
|
42
144
|
expect(store.get(notebookQueuedOrRunningCountAtom)).toBe(0);
|
|
43
145
|
});
|
|
44
146
|
|
|
45
|
-
it("forgets
|
|
46
|
-
const store =
|
|
47
|
-
|
|
48
|
-
submitCells(store, [C1]);
|
|
147
|
+
it("forgets requests and blocked cells with the replica they were made for", () => {
|
|
148
|
+
const { store } = shown();
|
|
149
|
+
request(store, [C1]);
|
|
49
150
|
observeCellMessage(store, cellOp("disabled-transitively"));
|
|
50
151
|
|
|
51
|
-
MockExecutions.seed(store, []);
|
|
152
|
+
MockExecutions.seed(store, [], null, documentOf());
|
|
52
153
|
|
|
53
154
|
const view = cellExecutionView(store.get(cellExecutionInputsAtom), C1);
|
|
54
155
|
expect(view.status).toBe("idle");
|
|
@@ -56,43 +157,126 @@ describe("observeCellMessage", () => {
|
|
|
56
157
|
expect(store.get(notebookQueuedOrRunningCountAtom)).toBe(0);
|
|
57
158
|
});
|
|
58
159
|
|
|
59
|
-
it("keeps a
|
|
60
|
-
const store =
|
|
61
|
-
|
|
62
|
-
submitCells(store, [C1]);
|
|
160
|
+
it("keeps a request while the cell is merely queued", () => {
|
|
161
|
+
const { store } = shown();
|
|
162
|
+
request(store, [C1]);
|
|
63
163
|
|
|
64
164
|
observeCellMessage(store, cellOp("queued"));
|
|
65
165
|
|
|
66
|
-
expect(
|
|
67
|
-
cellExecutionView(store.get(cellExecutionInputsAtom), C1).status,
|
|
68
|
-
).toBe("queued");
|
|
166
|
+
expect(statusOf(store)).toBe("queued");
|
|
69
167
|
});
|
|
168
|
+
});
|
|
70
169
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const replica =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
});
|
|
170
|
+
describe("acceptRun", () => {
|
|
171
|
+
it("hands the request over to the run once the stream shows it", () => {
|
|
172
|
+
const { store, replica } = shown();
|
|
173
|
+
const made = request(store, [C1]);
|
|
174
|
+
acceptRun(store, made, run([C1]));
|
|
175
|
+
expect(statusOf(store)).toBe("queued");
|
|
176
|
+
expect(requested(store)).toEqual([C1]);
|
|
79
177
|
|
|
80
|
-
|
|
81
|
-
store.
|
|
82
|
-
store.set(replica.
|
|
83
|
-
expect(
|
|
84
|
-
|
|
85
|
-
).toBe(
|
|
86
|
-
|
|
87
|
-
const empty = MockExecutions.inputs([]);
|
|
88
|
-
store.set(replica.executionsAtom, empty.executions);
|
|
89
|
-
store.set(replica.resultsAtom, empty.results);
|
|
90
|
-
store.set(replica.notebookAtom, {
|
|
91
|
-
...store.get(replica.notebookAtom),
|
|
92
|
-
id: "n2",
|
|
93
|
-
});
|
|
178
|
+
// The response landed first, and the run event follows.
|
|
179
|
+
const stream = store.get(replica.executionsAtom);
|
|
180
|
+
store.set(replica.executionsAtom, withRuns(stream, [run([C1])]));
|
|
181
|
+
expect(requested(store)).toEqual([]);
|
|
182
|
+
expect(statusOf(store)).toBe("queued");
|
|
183
|
+
expect(store.get(notebookQueuedOrRunningCountAtom)).toBe(1);
|
|
94
184
|
|
|
95
|
-
|
|
185
|
+
// The run ends without ever planning the cell.
|
|
186
|
+
store.set(
|
|
187
|
+
replica.executionsAtom,
|
|
188
|
+
withRuns(stream, [run([C1], "completed")]),
|
|
189
|
+
);
|
|
190
|
+
expect(statusOf(store)).toBe("idle");
|
|
96
191
|
expect(store.get(notebookQueuedOrRunningCountAtom)).toBe(0);
|
|
97
192
|
});
|
|
193
|
+
|
|
194
|
+
it("hands over at once when the stream showed the run first", () => {
|
|
195
|
+
const { store, replica } = shown();
|
|
196
|
+
const made = request(store, [C1]);
|
|
197
|
+
const stream = store.get(replica.executionsAtom);
|
|
198
|
+
store.set(replica.executionsAtom, withRuns(stream, [run([C1])]));
|
|
199
|
+
|
|
200
|
+
acceptRun(store, made, run([C1]));
|
|
201
|
+
|
|
202
|
+
expect(requested(store)).toEqual([]);
|
|
203
|
+
expect(statusOf(store)).toBe("queued");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("leaves a later request for the same cell when an earlier run ends", () => {
|
|
207
|
+
const { store, replica } = shown();
|
|
208
|
+
const first = request(store, [C1]);
|
|
209
|
+
const second = request(store, [C1]);
|
|
210
|
+
acceptRun(store, first, run([C1]));
|
|
211
|
+
|
|
212
|
+
const stream = store.get(replica.executionsAtom);
|
|
213
|
+
store.set(replica.executionsAtom, withRuns(stream, [run([C1])]));
|
|
214
|
+
expect(requested(store)).toEqual([C1]);
|
|
215
|
+
store.set(
|
|
216
|
+
replica.executionsAtom,
|
|
217
|
+
withRuns(stream, [run([C1], "completed")]),
|
|
218
|
+
);
|
|
219
|
+
expect(statusOf(store)).toBe("queued");
|
|
220
|
+
|
|
221
|
+
acceptRun(store, second, run([C1], "queued", "run-2"));
|
|
222
|
+
store.set(
|
|
223
|
+
replica.executionsAtom,
|
|
224
|
+
withRuns(stream, [run([C1], "completed"), run([C1], "queued", "run-2")]),
|
|
225
|
+
);
|
|
226
|
+
expect(requested(store)).toEqual([]);
|
|
227
|
+
expect(statusOf(store)).toBe("queued");
|
|
228
|
+
|
|
229
|
+
store.set(
|
|
230
|
+
replica.executionsAtom,
|
|
231
|
+
withRuns(stream, [run([C1], "completed", "run-2")]),
|
|
232
|
+
);
|
|
233
|
+
expect(statusOf(store)).toBe("idle");
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("keeps waiting while the stream is between a reset and its snapshot", () => {
|
|
237
|
+
const { store, replica } = shown();
|
|
238
|
+
const made = request(store, [C1]);
|
|
239
|
+
acceptRun(store, made, run([C1]));
|
|
240
|
+
|
|
241
|
+
const notebook = store.get(replica.notebookAtom);
|
|
242
|
+
store.set(replica.notebookAtom, null);
|
|
243
|
+
const stream = store.get(replica.executionsAtom);
|
|
244
|
+
store.set(replica.executionsAtom, { ...stream });
|
|
245
|
+
store.set(replica.notebookAtom, notebook);
|
|
246
|
+
expect(requested(store)).toEqual([C1]);
|
|
247
|
+
expect(statusOf(store)).toBe("queued");
|
|
248
|
+
|
|
249
|
+
store.set(replica.executionsAtom, withRuns(stream, [run([C1])]));
|
|
250
|
+
expect(requested(store)).toEqual([]);
|
|
251
|
+
expect(statusOf(store)).toBe("queued");
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("drops the request once the replica shows another notebook", () => {
|
|
255
|
+
const { store, replica } = shown();
|
|
256
|
+
const made = request(store, [C1]);
|
|
257
|
+
acceptRun(store, made, run([C1]));
|
|
258
|
+
|
|
259
|
+
store.set(replica.notebookAtom, MockExecutions.notebook("n2"));
|
|
260
|
+
const stream = store.get(replica.executionsAtom);
|
|
261
|
+
store.set(replica.executionsAtom, withRuns(stream, [run([C1])]));
|
|
262
|
+
store.set(replica.notebookAtom, MockExecutions.notebook("n1"));
|
|
263
|
+
|
|
264
|
+
expect(requested(store)).toEqual([]);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("stops following a run once the replica changes", () => {
|
|
268
|
+
const { store, replica } = shown();
|
|
269
|
+
const made = request(store, [C1]);
|
|
270
|
+
acceptRun(store, made, run([C1]));
|
|
271
|
+
|
|
272
|
+
MockExecutions.seed(store, [], null, documentOf());
|
|
273
|
+
request(store, [C1]);
|
|
274
|
+
const stream = store.get(replica.executionsAtom);
|
|
275
|
+
store.set(
|
|
276
|
+
replica.executionsAtom,
|
|
277
|
+
withRuns(stream, [run([C1], "completed")]),
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
expect(statusOf(store)).toBe("queued");
|
|
281
|
+
});
|
|
98
282
|
});
|
|
@@ -143,8 +143,7 @@ describe("useCellOutput", () => {
|
|
|
143
143
|
expect(result.current).toBeNull();
|
|
144
144
|
|
|
145
145
|
act(() => {
|
|
146
|
-
|
|
147
|
-
store.set(replica.notebookAtom, { ...notebook, id: "n2" });
|
|
146
|
+
store.set(replica.notebookAtom, MockExecutions.notebook("n2"));
|
|
148
147
|
});
|
|
149
148
|
expect(result.current?.data).toBe("c1 value");
|
|
150
149
|
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { MockExecutions } from "@/__mocks__/executions";
|
|
5
|
+
import { document as documentOf } from "@/core/notebook/document/__tests__/fixtures";
|
|
6
|
+
import {
|
|
7
|
+
type FakeInput,
|
|
8
|
+
fakeBackend,
|
|
9
|
+
} from "@/core/notebook/replica/__tests__/fixtures";
|
|
10
|
+
import { activeNotebookReplicaAtom } from "@/core/session/active-notebook";
|
|
11
|
+
import { store } from "@/core/state/jotai";
|
|
12
|
+
import { replyInput } from "../input";
|
|
13
|
+
|
|
14
|
+
const toast = vi.fn();
|
|
15
|
+
vi.mock("@/components/ui/use-toast", () => ({
|
|
16
|
+
toast: (...args: unknown[]) => toast(...args),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
describe("replyInput", () => {
|
|
20
|
+
let input: ReturnType<typeof vi.fn<FakeInput>>;
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
toast.mockClear();
|
|
24
|
+
input = vi.fn<FakeInput>(async (_notebookId, inputId) => ({
|
|
25
|
+
ok: true,
|
|
26
|
+
status: 200,
|
|
27
|
+
data: {
|
|
28
|
+
id: inputId,
|
|
29
|
+
execution_id: "e1",
|
|
30
|
+
seq: 0,
|
|
31
|
+
prompt: "Name: ",
|
|
32
|
+
password: false,
|
|
33
|
+
status: "answered",
|
|
34
|
+
},
|
|
35
|
+
}));
|
|
36
|
+
const replica = MockExecutions.seed(
|
|
37
|
+
store,
|
|
38
|
+
[],
|
|
39
|
+
MockExecutions.runtime("running"),
|
|
40
|
+
documentOf(),
|
|
41
|
+
);
|
|
42
|
+
store.set(replica.backendAtom, fakeBackend({ input }));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
store.set(activeNotebookReplicaAtom, null);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("answers the prompt by id in the connected notebook", async () => {
|
|
50
|
+
await replyInput(store, "prompt-1", "marimo");
|
|
51
|
+
|
|
52
|
+
expect(input).toHaveBeenCalledWith("n1", "prompt-1", { value: "marimo" });
|
|
53
|
+
expect(toast).not.toHaveBeenCalled();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("reports a reply the host refuses", async () => {
|
|
57
|
+
input.mockResolvedValue({
|
|
58
|
+
ok: false,
|
|
59
|
+
status: 409,
|
|
60
|
+
problem: {
|
|
61
|
+
type: "tag:marimo.io,2026:host/conflict",
|
|
62
|
+
title: "Conflict",
|
|
63
|
+
status: 409,
|
|
64
|
+
detail: "input prompt-1 has already ended.",
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await replyInput(store, "prompt-1", "marimo");
|
|
69
|
+
|
|
70
|
+
expect(toast).toHaveBeenCalledWith(
|
|
71
|
+
expect.objectContaining({
|
|
72
|
+
title: "Failed to send input",
|
|
73
|
+
description: "input prompt-1 has already ended.",
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("does nothing without a connected notebook", async () => {
|
|
79
|
+
store.set(activeNotebookReplicaAtom, null);
|
|
80
|
+
|
|
81
|
+
await replyInput(store, "prompt-1", "marimo");
|
|
82
|
+
|
|
83
|
+
expect(input).not.toHaveBeenCalled();
|
|
84
|
+
expect(toast).not.toHaveBeenCalled();
|
|
85
|
+
});
|
|
86
|
+
});
|