@marimo-team/islands 0.25.3-dev223 → 0.25.3-dev224
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/{ErrorBoundary-BToheQV2.js → ErrorBoundary-Ba6GHuhB.js} +1 -1
- package/dist/{code-visibility-CO0hOr56.js → code-visibility-BXNYYFb5.js} +1 -1
- package/dist/{common-CV0669k1.js → common-BHSOMSDG.js} +1 -1
- package/dist/{data-grid-overlay-editor-CwDPMtXI.js → data-grid-overlay-editor-CGLPw73V.js} +1 -1
- package/dist/{feedback-button-RB_OfVUt.js → feedback-button-BUf6rqKQ.js} +2 -2
- package/dist/{glide-data-editor-B2h5fYfH.js → glide-data-editor-Dm3DXKHl.js} +3 -3
- package/dist/{globals-YR9OeOwq.js → globals-DjSaXJeh.js} +1 -1
- package/dist/main.js +4179 -4062
- package/dist/{number-overlay-editor-BY4yYxfZ.js → number-overlay-editor-BVAnAmYf.js} +1 -1
- package/dist/{reveal-component-GiGQpBMz.js → reveal-component-Cfoa8xAb.js} +2 -2
- package/package.json +1 -1
- package/src/core/notebook/replica/__tests__/executions.test.ts +201 -0
- package/src/core/notebook/replica/__tests__/replica.test.ts +6 -0
- package/src/core/notebook/replica/__tests__/results.test.ts +193 -0
- package/src/core/notebook/replica/index.ts +7 -0
- package/src/core/notebook/replica/replica.ts +34 -2
- package/src/core/notebook/replica/resources/executions.ts +157 -0
- package/src/core/notebook/replica/resources/results.ts +48 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
|
|
2
2
|
import { t } from "./react-sHtZdjeP.js";
|
|
3
|
-
import { a as n } from "./glide-data-editor-
|
|
3
|
+
import { a as n } from "./glide-data-editor-Dm3DXKHl.js";
|
|
4
4
|
//#region ../node_modules/.pnpm/@glideapps+glide-data-grid@6.0.4-alpha24_lodash@4.17.21_marked@15.0.12_react-dom@19.3.0_27cffe7f1bc30287d59ac27db3240c5b/node_modules/@glideapps/glide-data-grid/dist/esm/internal/data-grid-overlay-editor/private/number-overlay-editor-style.js
|
|
5
5
|
var r = /* @__PURE__ */ e(t()), i = /*#__PURE__*/ n("div")({
|
|
6
6
|
name: "NumberOverlayEditorStyle",
|
|
@@ -3,7 +3,7 @@ import { c as t, g as n, h as r, m as i, t as a } from "./button-B6RjDKJx.js";
|
|
|
3
3
|
import { t as o } from "./react-sHtZdjeP.js";
|
|
4
4
|
import { t as s } from "./compiler-runtime-CldRdSsv.js";
|
|
5
5
|
import { m as c, t as l } from "./tooltip-C3UnvsRe.js";
|
|
6
|
-
import { $ as u, Q as d, Z as f, an as p, i as m, l as h, ln as g, n as _, on as v, r as y, t as b, u as x } from "./common-
|
|
6
|
+
import { $ as u, Q as d, Z as f, an as p, i as m, l as h, ln as g, n as _, on as v, r as y, t as b, u as x } from "./common-BHSOMSDG.js";
|
|
7
7
|
import { M as S } from "./select-BSxlITBo.js";
|
|
8
8
|
import { Y as ee } from "./dropdown-menu-CSNdPSnS.js";
|
|
9
9
|
import { Pt as C } from "./error-entries-CRPfxMqm.js";
|
|
@@ -11,7 +11,7 @@ import { t as w } from "./jsx-runtime-3BD1kT0s.js";
|
|
|
11
11
|
import { r as T } from "./use-restore-focus-BWO0z1lo.js";
|
|
12
12
|
import { D as E, t as D, x as O } from "./utils-DXkj82sK.js";
|
|
13
13
|
import { o as k } from "./static-state-BldGd99T.js";
|
|
14
|
-
import { a as A, n as te, t as ne } from "./code-visibility-
|
|
14
|
+
import { a as A, n as te, t as ne } from "./code-visibility-BXNYYFb5.js";
|
|
15
15
|
/* empty css */
|
|
16
16
|
var j = c("sticky-note", [["path", {
|
|
17
17
|
d: "M21 9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2z",
|
package/package.json
CHANGED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import * as Protocol from "@marimo-team/client/protocol";
|
|
5
|
+
import { executions, itemsInOrder } from "../resources/executions";
|
|
6
|
+
import { createNotebookReducer } from "@marimo-team/client";
|
|
7
|
+
import { fixtureEvents } from "./fixtures";
|
|
8
|
+
|
|
9
|
+
const reducer = createNotebookReducer({ executions });
|
|
10
|
+
const snapshot = fixtureEvents("notebook-events.sse");
|
|
11
|
+
const runCells = fixtureEvents("run-cells.sse");
|
|
12
|
+
const inputs = fixtureEvents("inputs.sse");
|
|
13
|
+
const time = "2026-10-01T09:30:00Z";
|
|
14
|
+
|
|
15
|
+
function replay(
|
|
16
|
+
events: readonly Protocol.EventOutput[],
|
|
17
|
+
from = reducer.initial,
|
|
18
|
+
) {
|
|
19
|
+
return events.reduce(reducer.reduce, from);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const resource = (state: ReturnType<typeof replay>) =>
|
|
23
|
+
state.resources.executions;
|
|
24
|
+
|
|
25
|
+
/** Build one event as the stream would deliver it. */
|
|
26
|
+
const event = (message: unknown) => Protocol.Event.parse(message);
|
|
27
|
+
|
|
28
|
+
const removed = (kind: string, id: string, cursor: string) =>
|
|
29
|
+
event({ event: `${kind}.removed`, id: cursor, data: { id, time } });
|
|
30
|
+
|
|
31
|
+
/** How the host retires a cell execution: its items first, then itself. */
|
|
32
|
+
const retire = (executionId: string, variableId: string) => [
|
|
33
|
+
removed("variable", variableId, "111"),
|
|
34
|
+
removed("execution", executionId, "112"),
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const ids = (holder: Parameters<typeof itemsInOrder>[0]) =>
|
|
38
|
+
itemsInOrder(holder).map((item) => item.id);
|
|
39
|
+
|
|
40
|
+
describe("executions resource", () => {
|
|
41
|
+
it("keeps scratch work from the snapshot and completes it after ready", () => {
|
|
42
|
+
const ready = snapshot.findIndex((item) => item.event === "ready");
|
|
43
|
+
const opening = resource(replay(snapshot.slice(0, ready + 1)));
|
|
44
|
+
const scratch = opening.executions.get("e7");
|
|
45
|
+
expect(scratch?.record.status).toBe("running");
|
|
46
|
+
expect(itemsInOrder(scratch!).map((item) => item.kind)).toEqual([
|
|
47
|
+
"console",
|
|
48
|
+
]);
|
|
49
|
+
expect(opening.runs.get("run-scratch")?.status).toBe("running");
|
|
50
|
+
expect(opening.latest.size).toBe(0);
|
|
51
|
+
|
|
52
|
+
const done = resource(replay(snapshot));
|
|
53
|
+
expect(done.executions.get("e7")?.record.status).toBe("completed");
|
|
54
|
+
expect(done.runs.get("run-scratch")?.outcome).toBe("succeeded");
|
|
55
|
+
const released = reducer.reduce(
|
|
56
|
+
replay(snapshot),
|
|
57
|
+
removed("run", "run-scratch", "98"),
|
|
58
|
+
);
|
|
59
|
+
expect(resource(released).runs.has("run-scratch")).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("indexes a cell's execution while it is on the stream", () => {
|
|
63
|
+
const start = replay(snapshot);
|
|
64
|
+
const running = resource(replay(runCells.slice(0, 5), start));
|
|
65
|
+
expect(running.latest.get("c3")).toBe("e12");
|
|
66
|
+
expect(running.executions.get("e12")?.record.status).toBe("running");
|
|
67
|
+
expect(running.latest.get("k7f2")).toBe("e13");
|
|
68
|
+
expect(running.runs.get("run-cells")?.status).toBe("running");
|
|
69
|
+
|
|
70
|
+
const done = replay(runCells, start);
|
|
71
|
+
const e12 = resource(done).executions.get("e12");
|
|
72
|
+
expect(e12?.record.status).toBe("completed");
|
|
73
|
+
expect([...e12!.variables.values()].map((v) => v.name)).toEqual(["x"]);
|
|
74
|
+
expect(resource(done).executions.get("e13")?.record.depends_on).toEqual({
|
|
75
|
+
x: { execution: "e12", update: 0 },
|
|
76
|
+
});
|
|
77
|
+
expect(resource(done).runs.get("run-cells")?.outcome).toBe("succeeded");
|
|
78
|
+
|
|
79
|
+
const retired = replay(retire("e12", "e12-x"), done);
|
|
80
|
+
expect(resource(retired).executions.has("e12")).toBe(false);
|
|
81
|
+
expect(resource(retired).latest.has("c3")).toBe(false);
|
|
82
|
+
const queued = reducer.reduce(
|
|
83
|
+
retired,
|
|
84
|
+
event({
|
|
85
|
+
event: "execution",
|
|
86
|
+
id: "113",
|
|
87
|
+
data: {
|
|
88
|
+
execution: {
|
|
89
|
+
...e12!.record,
|
|
90
|
+
id: "e14",
|
|
91
|
+
run_id: "run-again",
|
|
92
|
+
status: "queued",
|
|
93
|
+
outcome: null,
|
|
94
|
+
started_at: null,
|
|
95
|
+
completed_at: null,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
99
|
+
);
|
|
100
|
+
expect(resource(queued).latest.get("c3")).toBe("e14");
|
|
101
|
+
expect([...resource(queued).executions.keys()]).toEqual([
|
|
102
|
+
"e7",
|
|
103
|
+
"e13",
|
|
104
|
+
"e14",
|
|
105
|
+
]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("applies item and variable removals to the stream's copy", () => {
|
|
109
|
+
const done = replay(runCells, replay(snapshot));
|
|
110
|
+
const late = {
|
|
111
|
+
id: "o9",
|
|
112
|
+
execution_id: "e12",
|
|
113
|
+
seq: 0,
|
|
114
|
+
name: "stdout",
|
|
115
|
+
text: "late\n",
|
|
116
|
+
};
|
|
117
|
+
const shown = {
|
|
118
|
+
id: "d1",
|
|
119
|
+
execution_id: "e12",
|
|
120
|
+
seq: 1,
|
|
121
|
+
mimetype: "text/plain",
|
|
122
|
+
data: "figure",
|
|
123
|
+
};
|
|
124
|
+
const added = replay(
|
|
125
|
+
[
|
|
126
|
+
event({ event: "console", id: "111", data: { console: late } }),
|
|
127
|
+
event({ event: "display", id: "112", data: { display: shown } }),
|
|
128
|
+
],
|
|
129
|
+
done,
|
|
130
|
+
);
|
|
131
|
+
expect(ids(resource(added).executions.get("e12")!)).toEqual(["o9", "d1"]);
|
|
132
|
+
|
|
133
|
+
const cleared = replay(
|
|
134
|
+
[
|
|
135
|
+
removed("display", "d1", "113"),
|
|
136
|
+
removed("console", "o9", "114"),
|
|
137
|
+
removed("variable", "e12-x", "115"),
|
|
138
|
+
],
|
|
139
|
+
added,
|
|
140
|
+
);
|
|
141
|
+
const e12 = resource(cleared).executions.get("e12")!;
|
|
142
|
+
expect(ids(e12)).toEqual([]);
|
|
143
|
+
expect(e12.variables.size).toBe(0);
|
|
144
|
+
expect(resource(cleared).latest.get("c3")).toBe("e12");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("orders items by seq, replaces them by id, and drops orphans", () => {
|
|
148
|
+
const start = replay(snapshot);
|
|
149
|
+
const prompted = resource(replay(inputs.slice(0, -1), start));
|
|
150
|
+
const execution = prompted.executions.get("e-input");
|
|
151
|
+
expect(
|
|
152
|
+
itemsInOrder(execution!).map((item) => [
|
|
153
|
+
item.kind,
|
|
154
|
+
item.id,
|
|
155
|
+
item.kind === "input" ? item.status : item.seq,
|
|
156
|
+
]),
|
|
157
|
+
).toEqual([
|
|
158
|
+
["console", "o-input-0", 0],
|
|
159
|
+
["input", "i1", "answered"],
|
|
160
|
+
["console", "o-input-2", 2],
|
|
161
|
+
["input", "i2", "cancelled"],
|
|
162
|
+
]);
|
|
163
|
+
expect(prompted.latest.size).toBe(0);
|
|
164
|
+
const gone = resource(replay(inputs, start));
|
|
165
|
+
expect(gone.executions.has("e-input")).toBe(false);
|
|
166
|
+
expect(gone.runs.get("run-input")?.outcome).toBe("interrupted");
|
|
167
|
+
|
|
168
|
+
const orphan = reducer.reduce(
|
|
169
|
+
start,
|
|
170
|
+
event({
|
|
171
|
+
event: "console",
|
|
172
|
+
id: "400",
|
|
173
|
+
data: {
|
|
174
|
+
console: {
|
|
175
|
+
id: "o-orphan",
|
|
176
|
+
execution_id: "nope",
|
|
177
|
+
seq: 0,
|
|
178
|
+
name: "stdout",
|
|
179
|
+
text: "",
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
}),
|
|
183
|
+
);
|
|
184
|
+
expect(orphan.resources).toBe(start.resources);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("ignores removals delivered again", () => {
|
|
188
|
+
const retired = replay(
|
|
189
|
+
retire("e12", "e12-x"),
|
|
190
|
+
replay(runCells, replay(snapshot)),
|
|
191
|
+
);
|
|
192
|
+
const again = replay(retire("e12", "e12-x"), retired);
|
|
193
|
+
expect(again.resources).toBe(retired.resources);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("clears on reset", () => {
|
|
197
|
+
const done = replay(runCells, replay(snapshot));
|
|
198
|
+
const reset = reducer.reduce(done, { event: "reset", data: {} });
|
|
199
|
+
expect(resource(reset)).toBe(executions.initial);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
@@ -231,6 +231,8 @@ describe("NotebookReplica lifetime", () => {
|
|
|
231
231
|
"k7f2",
|
|
232
232
|
]);
|
|
233
233
|
expect(store.get(client.runtimeAtom)?.id).toBe("r1");
|
|
234
|
+
expect(store.get(client.executionsAtom).runs.has("run-scratch")).toBe(true);
|
|
235
|
+
expect(store.get(client.resultsAtom).size).toBe(3);
|
|
234
236
|
attempt.drop();
|
|
235
237
|
await backoff();
|
|
236
238
|
expect(store.get(client.documentAtom)?.revision.order).toEqual([
|
|
@@ -239,8 +241,12 @@ describe("NotebookReplica lifetime", () => {
|
|
|
239
241
|
"k7f2",
|
|
240
242
|
]);
|
|
241
243
|
expect(store.get(client.runtimeAtom)).toBeNull();
|
|
244
|
+
expect(store.get(client.executionsAtom).runs.has("run-scratch")).toBe(true);
|
|
245
|
+
expect(store.get(client.resultsAtom).size).toBe(3);
|
|
242
246
|
current.abort();
|
|
243
247
|
expect(store.get(client.documentAtom)).toBeNull();
|
|
248
|
+
expect(store.get(client.executionsAtom).runs.size).toBe(0);
|
|
249
|
+
expect(store.get(client.resultsAtom).size).toBe(0);
|
|
244
250
|
});
|
|
245
251
|
|
|
246
252
|
it.each([
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import * as Protocol from "@marimo-team/client/protocol";
|
|
5
|
+
import { results } from "../resources/results";
|
|
6
|
+
import { createNotebookReducer } from "@marimo-team/client";
|
|
7
|
+
import { fixtureEvents } from "./fixtures";
|
|
8
|
+
|
|
9
|
+
const reducer = createNotebookReducer({ results });
|
|
10
|
+
const snapshot = fixtureEvents("notebook-events.sse");
|
|
11
|
+
const runCells = fixtureEvents("run-cells.sse");
|
|
12
|
+
const time = "2026-10-01T09:30:00Z";
|
|
13
|
+
|
|
14
|
+
function replay(
|
|
15
|
+
events: readonly Protocol.EventOutput[],
|
|
16
|
+
from = reducer.initial,
|
|
17
|
+
) {
|
|
18
|
+
return events.reduce(reducer.reduce, from);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const resource = (state: ReturnType<typeof replay>) => state.resources.results;
|
|
22
|
+
|
|
23
|
+
/** Build one event as the stream would deliver it. */
|
|
24
|
+
const event = (message: unknown) => Protocol.Event.parse(message);
|
|
25
|
+
|
|
26
|
+
const removed = (kind: string, id: string, cursor: string) =>
|
|
27
|
+
event({ event: `${kind}.removed`, id: cursor, data: { id, time } });
|
|
28
|
+
|
|
29
|
+
/** How the host retires a cell execution: its items first, then itself. */
|
|
30
|
+
const retire = (executionId: string, variableId: string) => [
|
|
31
|
+
removed("variable", variableId, "111"),
|
|
32
|
+
removed("execution", executionId, "112"),
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const restored = {
|
|
36
|
+
id: "e-restored",
|
|
37
|
+
run_id: null,
|
|
38
|
+
runtime_id: null,
|
|
39
|
+
caused_by: null,
|
|
40
|
+
cell_id: "c3",
|
|
41
|
+
code: null,
|
|
42
|
+
code_hash: "0f71456db2ea83f4",
|
|
43
|
+
depends_on: {},
|
|
44
|
+
status: "completed",
|
|
45
|
+
outcome: { kind: "value", display: null },
|
|
46
|
+
started_at: "2026-09-30T09:20:01Z",
|
|
47
|
+
completed_at: "2026-09-30T09:20:02Z",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
describe("results resource", () => {
|
|
51
|
+
it("has an entry per document cell, filled by a restored result", () => {
|
|
52
|
+
const ready = snapshot.findIndex((item) => item.event === "ready");
|
|
53
|
+
const opening = resource(replay(snapshot.slice(0, ready + 1)));
|
|
54
|
+
expect([...opening]).toEqual([
|
|
55
|
+
["c1", null],
|
|
56
|
+
["c3", null],
|
|
57
|
+
["k7f2", null],
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
const opened = resource(
|
|
61
|
+
replay([
|
|
62
|
+
...snapshot.slice(0, ready),
|
|
63
|
+
event({ event: "execution", id: "94", data: { execution: restored } }),
|
|
64
|
+
...snapshot.slice(ready),
|
|
65
|
+
]),
|
|
66
|
+
);
|
|
67
|
+
expect(opened.get("c3")?.id).toBe("e-restored");
|
|
68
|
+
expect(opened.get("c3")?.run_id).toBeNull();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("records a cell's result when its execution completes", () => {
|
|
72
|
+
const start = replay(snapshot);
|
|
73
|
+
const running = resource(replay(runCells.slice(0, 5), start));
|
|
74
|
+
expect(running.get("c3")).toBeNull();
|
|
75
|
+
expect(running.get("k7f2")).toBeNull();
|
|
76
|
+
|
|
77
|
+
const done = resource(replay(runCells, start));
|
|
78
|
+
expect(done.get("c3")?.id).toBe("e12");
|
|
79
|
+
expect(done.get("c3")?.status).toBe("completed");
|
|
80
|
+
expect(done.get("k7f2")?.depends_on).toEqual({
|
|
81
|
+
x: { execution: "e12", update: 0 },
|
|
82
|
+
});
|
|
83
|
+
expect(done.get("c1")).toBeNull();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("keeps a result while the host replaces it", () => {
|
|
87
|
+
const done = replay(runCells, replay(snapshot));
|
|
88
|
+
const result = resource(done).get("c3")!;
|
|
89
|
+
const retired = replay(retire("e12", "e12-x"), done);
|
|
90
|
+
expect(resource(retired).get("c3")).toBe(result);
|
|
91
|
+
|
|
92
|
+
const again = {
|
|
93
|
+
...result,
|
|
94
|
+
id: "e14",
|
|
95
|
+
run_id: "run-again",
|
|
96
|
+
status: "queued",
|
|
97
|
+
outcome: null,
|
|
98
|
+
started_at: null,
|
|
99
|
+
completed_at: null,
|
|
100
|
+
};
|
|
101
|
+
const queued = reducer.reduce(
|
|
102
|
+
retired,
|
|
103
|
+
event({ event: "execution", id: "113", data: { execution: again } }),
|
|
104
|
+
);
|
|
105
|
+
expect(resource(queued).get("c3")).toBe(result);
|
|
106
|
+
|
|
107
|
+
const completed = reducer.reduce(
|
|
108
|
+
queued,
|
|
109
|
+
event({
|
|
110
|
+
event: "execution",
|
|
111
|
+
id: "114",
|
|
112
|
+
data: {
|
|
113
|
+
execution: {
|
|
114
|
+
...again,
|
|
115
|
+
status: "completed",
|
|
116
|
+
outcome: { kind: "value", display: null },
|
|
117
|
+
started_at: time,
|
|
118
|
+
completed_at: time,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
}),
|
|
122
|
+
);
|
|
123
|
+
expect(resource(completed).get("c3")?.id).toBe("e14");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("replaces a result with an execution interrupted before it started", () => {
|
|
127
|
+
const done = replay(runCells, replay(snapshot));
|
|
128
|
+
const result = resource(done).get("c3")!;
|
|
129
|
+
const interrupted = {
|
|
130
|
+
...result,
|
|
131
|
+
id: "e14",
|
|
132
|
+
run_id: "run-again",
|
|
133
|
+
status: "completed",
|
|
134
|
+
outcome: { kind: "interrupted" },
|
|
135
|
+
started_at: null,
|
|
136
|
+
completed_at: time,
|
|
137
|
+
};
|
|
138
|
+
const cancelled = replay(
|
|
139
|
+
[
|
|
140
|
+
...retire("e12", "e12-x"),
|
|
141
|
+
event({
|
|
142
|
+
event: "execution",
|
|
143
|
+
id: "113",
|
|
144
|
+
data: {
|
|
145
|
+
execution: {
|
|
146
|
+
...interrupted,
|
|
147
|
+
status: "queued",
|
|
148
|
+
outcome: null,
|
|
149
|
+
completed_at: null,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
}),
|
|
153
|
+
event({
|
|
154
|
+
event: "execution",
|
|
155
|
+
id: "114",
|
|
156
|
+
data: { execution: interrupted },
|
|
157
|
+
}),
|
|
158
|
+
],
|
|
159
|
+
done,
|
|
160
|
+
);
|
|
161
|
+
expect(resource(cancelled).get("c3")?.outcome).toEqual({
|
|
162
|
+
kind: "interrupted",
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("drops a cell deleted while its execution runs", () => {
|
|
167
|
+
const start = replay(runCells.slice(0, 5), replay(snapshot));
|
|
168
|
+
const deleted = reducer.reduce(start, removed("cell", "c3", "105"));
|
|
169
|
+
expect(resource(deleted).has("c3")).toBe(false);
|
|
170
|
+
|
|
171
|
+
const finished = replay(runCells.slice(5), deleted);
|
|
172
|
+
expect(resource(finished).has("c3")).toBe(false);
|
|
173
|
+
expect(resource(finished).get("k7f2")?.id).toBe("e13");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("ignores cells it has not seen and clears on reset", () => {
|
|
177
|
+
const start = replay(snapshot);
|
|
178
|
+
const unknown = reducer.reduce(
|
|
179
|
+
start,
|
|
180
|
+
event({
|
|
181
|
+
event: "execution",
|
|
182
|
+
id: "98",
|
|
183
|
+
data: { execution: { ...restored, id: "e-zz", cell_id: "zz" } },
|
|
184
|
+
}),
|
|
185
|
+
);
|
|
186
|
+
expect(unknown.resources).toBe(start.resources);
|
|
187
|
+
const reset = reducer.reduce(replay(runCells, start), {
|
|
188
|
+
event: "reset",
|
|
189
|
+
data: {},
|
|
190
|
+
});
|
|
191
|
+
expect(resource(reset)).toBe(results.initial);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -11,3 +11,10 @@ export {
|
|
|
11
11
|
} from "./connection";
|
|
12
12
|
export { createNotebookReplica, type NotebookReplica } from "./replica";
|
|
13
13
|
export type { Document } from "./resources/document";
|
|
14
|
+
export {
|
|
15
|
+
type Execution,
|
|
16
|
+
type Executions,
|
|
17
|
+
type Item,
|
|
18
|
+
itemsInOrder,
|
|
19
|
+
} from "./resources/executions";
|
|
20
|
+
export type { Results } from "./resources/results";
|
|
@@ -24,15 +24,26 @@ import {
|
|
|
24
24
|
} from "./connection";
|
|
25
25
|
import { attachments } from "./resources/attachments";
|
|
26
26
|
import { type Document, document } from "./resources/document";
|
|
27
|
+
import { type Executions, executions } from "./resources/executions";
|
|
28
|
+
import { type Results, results } from "./resources/results";
|
|
27
29
|
import { runtime } from "./resources/runtime";
|
|
28
30
|
|
|
29
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* The kinds the editor reads, with marimo's own notifications.
|
|
33
|
+
*
|
|
34
|
+
* Console and display items stay out until the editor reads outputs from
|
|
35
|
+
* the replica. The native notifications still carry them.
|
|
36
|
+
*/
|
|
30
37
|
const TYPES = [
|
|
31
38
|
"notebook",
|
|
32
39
|
"cell",
|
|
33
40
|
"document",
|
|
34
41
|
"attachment",
|
|
35
42
|
"runtime",
|
|
43
|
+
"run",
|
|
44
|
+
"execution",
|
|
45
|
+
"input",
|
|
46
|
+
"variable",
|
|
36
47
|
"marimo",
|
|
37
48
|
] as const;
|
|
38
49
|
|
|
@@ -66,6 +77,17 @@ export interface NotebookReplica {
|
|
|
66
77
|
readonly attachmentsAtom: Atom<readonly Attachment[]>;
|
|
67
78
|
/** The notebook's runtime, null while it has none or the stream is not ready. */
|
|
68
79
|
readonly runtimeAtom: Atom<Runtime | null>;
|
|
80
|
+
/**
|
|
81
|
+
* Runs and executions as the stream reports them.
|
|
82
|
+
*
|
|
83
|
+
* Visible as events arrive, including during a snapshot, so a consumer
|
|
84
|
+
* joins with `documentAtom`, which is null until the snapshot is ready.
|
|
85
|
+
* Kept across a temporary disconnection, like the document, and cleared
|
|
86
|
+
* on reset or removal.
|
|
87
|
+
*/
|
|
88
|
+
readonly executionsAtom: Atom<Executions>;
|
|
89
|
+
/** Each document cell's last completed execution record, kept while it runs again. */
|
|
90
|
+
readonly resultsAtom: Atom<Results>;
|
|
69
91
|
/**
|
|
70
92
|
* Follow a backend, replacing any current connection.
|
|
71
93
|
*
|
|
@@ -124,7 +146,13 @@ export function createNotebookReplica(
|
|
|
124
146
|
state: ConnectionState.NOT_STARTED,
|
|
125
147
|
});
|
|
126
148
|
// One atom holds the whole stream, which resource atoms derive from.
|
|
127
|
-
const reducer = createNotebookReducer({
|
|
149
|
+
const reducer = createNotebookReducer({
|
|
150
|
+
document,
|
|
151
|
+
attachments,
|
|
152
|
+
runtime,
|
|
153
|
+
executions,
|
|
154
|
+
results,
|
|
155
|
+
});
|
|
128
156
|
const streamAtom = atom(reducer.initial);
|
|
129
157
|
// A resumed stream is live, but presence waits for this connection's ready.
|
|
130
158
|
const readyAtom = atom(false);
|
|
@@ -146,6 +174,8 @@ export function createNotebookReplica(
|
|
|
146
174
|
const runtimeAtom = atom((get) =>
|
|
147
175
|
get(presentAtom) ? get(streamAtom).resources.runtime : null,
|
|
148
176
|
);
|
|
177
|
+
const executionsAtom = atom((get) => get(streamAtom).resources.executions);
|
|
178
|
+
const resultsAtom = atom((get) => get(streamAtom).resources.results);
|
|
149
179
|
const backendAtom = atom<NotebookBackend | null>(null);
|
|
150
180
|
/** The connection this replica follows. Connecting again ends it. */
|
|
151
181
|
let current: { end(): void; reconnect(): Promise<void> } | undefined;
|
|
@@ -168,6 +198,8 @@ export function createNotebookReplica(
|
|
|
168
198
|
backendAtom,
|
|
169
199
|
attachmentsAtom,
|
|
170
200
|
runtimeAtom,
|
|
201
|
+
executionsAtom,
|
|
202
|
+
resultsAtom,
|
|
171
203
|
connect,
|
|
172
204
|
reconnect: async () => current?.reconnect(),
|
|
173
205
|
};
|