@marimo-team/islands 0.25.3-dev170 → 0.25.3-dev173
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-BbSXV-Xa.js → ConnectedDataExplorerComponent-Le_OBfmt.js} +9 -9
- package/dist/{ErrorBoundary-NE86qEWZ.js → ErrorBoundary-wT3f3iJ1.js} +8 -8
- package/dist/{abnfDiagram-VCTEODGH-D7n07nXf.js → abnfDiagram-VCTEODGH-Tf1ghyQQ.js} +2 -2
- package/dist/{any-language-editor-3E24SnCV.js → any-language-editor-B92WMzoC.js} +2 -2
- package/dist/{architectureDiagram-5GKGNRK7-D6CjRib5.js → architectureDiagram-5GKGNRK7-xlnbTG0T.js} +1 -1
- package/dist/{chat-ui-CC7GekpG.js → chat-ui-CuORr6fL.js} +70 -70
- package/dist/{check-SpOMoCLP.js → check-C9Mp9eCK.js} +1 -1
- package/dist/{chunk-IMKFNOWR-6dtPz7uS.js → chunk-IMKFNOWR-Cmd4a5jy.js} +1 -1
- package/dist/{chunk-SHT3W25Y-BwnFNKDA.js → chunk-SHT3W25Y-mSNp-WpO.js} +1 -1
- package/dist/{chunk-SVP7TREG-D2eV4vBJ.js → chunk-SVP7TREG-CWC49D8X.js} +1 -1
- package/dist/{chunk-TICWLB2K-BDs4-_-r.js → chunk-TICWLB2K-De9FINtG.js} +1 -1
- package/dist/{classDiagram-ZZMXUADV-DCJnf_8v.js → classDiagram-ZZMXUADV-DweTkYdp.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-BOmSzPBD.js → classDiagram-v2-VYDZK3BY-A6MR2TiE.js} +2 -2
- package/dist/{code-visibility-Bzvc6ajl.js → code-visibility-ZReaeMaF.js} +5 -5
- package/dist/{common-Ciw4zkh2.js → common-DCevHg1m.js} +231 -231
- package/dist/{copy-icon-C1trdvVt.js → copy-icon-De584pZ4.js} +2 -2
- package/dist/{cynefinDiagram-5FMLGOSQ-Bln_Soky.js → cynefinDiagram-5FMLGOSQ-BSvo5le-.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-D-nf7WzW.js → dagre-GXQ25YYZ-tXRQslO6.js} +1 -1
- package/dist/{data-grid-overlay-editor-AQREmxI7.js → data-grid-overlay-editor-CTC-bJus.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-PsgSMkY2.js → diagram-S7CK7UJ4-r3t5VtwI.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-DfwfUuSw.js → diagram-UQ7AKVKN-qgbC8YnT.js} +1 -1
- package/dist/{diagram-VX7I27RA-C9_e-oMc.js → diagram-VX7I27RA-CiHxEwtp.js} +1 -1
- package/dist/{diagram-Z3DM3KII-CWLa5tuY.js → diagram-Z3DM3KII-D95u15Ev.js} +1 -1
- package/dist/{dialog-DgeqyrEZ.js → dialog-DVRfQuhD.js} +24 -24
- package/dist/{dropdown-menu-B6ftyzOg.js → dropdown-menu-DCMbbQ35.js} +691 -691
- package/dist/{ebnfDiagram-PWID7BFC-B1QRM58v.js → ebnfDiagram-PWID7BFC-P7e4bryK.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-DJK_FqvW.js → erDiagram-RLTQ6QDP-CUHZcmRj.js} +1 -1
- package/dist/{error-banner-D40pmfQQ.js → error-banner-DxJ3s8YO.js} +57 -57
- package/dist/{error-entries-DgK3wUJR.js → error-entries-CmWMEsY_.js} +984 -961
- package/dist/{feedback-button-8a6SETmT.js → feedback-button-DwXkmewo.js} +29 -29
- package/dist/{flowDiagram-HODETNUW-Uxc25XSJ.js → flowDiagram-HODETNUW-BGZ95AzI.js} +2 -2
- package/dist/{formats-DQCyPd5Z.js → formats-DI7HEVv7.js} +1 -1
- package/dist/{glide-data-editor-B70oke_v.js → glide-data-editor-BlmcP-lw.js} +10 -10
- package/dist/{glide-utils-Dz_F4pAY.js → glide-utils-D98laidb.js} +2 -2
- package/dist/{globals-Cxd5hNlI.js → globals-BdRitZ4p.js} +3 -3
- package/dist/{html-to-image-D4yG5Ru0.js → html-to-image-bpiQh7ND.js} +55 -55
- package/dist/{infoDiagram-27XIBGKW-DmjxqwL0.js → infoDiagram-27XIBGKW-BF0YX70D.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-NgCLHqQi.js → ishikawaDiagram-5VMMS53U-_itsAAn-.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-CZaG-iam.js → kanban-definition-UXKFOSKX-VNW2Beqh.js} +1 -1
- package/dist/{label-D9_-zSg-.js → label-Qvw1E97W.js} +1 -1
- package/dist/main.js +8159 -6508
- package/dist/{mermaid-CSvw7c71.js → mermaid-CZ6FrClS.js} +35 -35
- package/dist/{mindmap-definition-YA3MSWOX-DJYQBDH_.js → mindmap-definition-YA3MSWOX-DR4ripvd.js} +1 -1
- package/dist/{number-overlay-editor-ChK-SKUk.js → number-overlay-editor-DQG-hFHT.js} +1 -1
- package/dist/{pegDiagram-XKGWAZYB-CHKO_dAe.js → pegDiagram-XKGWAZYB-CFr3XlGl.js} +2 -2
- package/dist/{pieDiagram-E7YTZNPT-DVevbMAo.js → pieDiagram-E7YTZNPT-j91XYSLY.js} +1 -1
- package/dist/{process-output-DxvBAeL3.js → process-output-BzloOI7F.js} +4 -4
- package/dist/{railroadDiagram-O6MQD6OU-Detj1kcB.js → railroadDiagram-O6MQD6OU-Cd47ZV9E.js} +2 -2
- package/dist/{requirementDiagram-BXWQKSXE-BLFD0T_Z.js → requirementDiagram-BXWQKSXE-eJH6gLtX.js} +1 -1
- package/dist/{reveal-component-CXMXeDZD.js → reveal-component-D3r3vSTK.js} +9 -9
- package/dist/{select-CkF_GBxO.js → select-2KOiReNY.js} +324 -324
- package/dist/{sequenceDiagram-WJ2MYXX4-BZBgatQV.js → sequenceDiagram-WJ2MYXX4-DdfFEIrm.js} +1 -1
- package/dist/{spec-Mnkn3XEr.js → spec-h69nC3SI.js} +4 -4
- package/dist/{stateDiagram-D77RDMKH-4BZW9-6_.js → stateDiagram-D77RDMKH-DkjDvip8.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-C4sKC8Iq.js → stateDiagram-v2-MP3YSRHH-DBHACnOo.js} +2 -2
- package/dist/{swimlanes-42K2YHIH-SL5yeGe2.js → swimlanes-42K2YHIH-ogImlOmp.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-DqPmAYUv.js → swimlanesDiagram-VR7AAH4N-D6x-zsci.js} +2 -2
- package/dist/{timeline-definition-24CTP7MA-BfXUMWkq.js → timeline-definition-24CTP7MA-DQpLtsHD.js} +1 -1
- package/dist/{toDate-TMubKIVL.js → toDate-CDzoofJo.js} +2 -2
- package/dist/{toast-Btbve3Lm.js → toast-B5t9o6s_.js} +47 -47
- package/dist/{tooltip-CJ4Rqasp.js → tooltip-s4zZxwKe.js} +67 -67
- package/dist/{use-restore-focus-CmGwRNId.js → use-restore-focus-BgDTd2tc.js} +1 -1
- package/dist/{useAsyncData-DjMGFzFy.js → useAsyncData-Bn8SxUqM.js} +1 -1
- package/dist/{useDateFormatter-eCL_SdeG.js → useDateFormatter-D3Tkfzej.js} +2 -2
- package/dist/{useDeepCompareMemoize-BQR5TJot.js → useDeepCompareMemoize-CC2tnPCk.js} +1 -1
- package/dist/{useLifecycle-C_GvKWbQ.js → useLifecycle-BJlxHqWa.js} +2 -2
- package/dist/{useTheme-Bns2gJZ6.js → useTheme-DMfQYmlG.js} +85 -85
- package/dist/{vega-component-BsVptuhz.js → vega-component-CovWHi60.js} +7 -7
- package/dist/{vennDiagram-4TSXK5OY-CH65znGa.js → vennDiagram-4TSXK5OY-BD-SL_vq.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-z9pWKb2_.js → wardleyDiagram-VM6X3IG4-BDgJV4ah.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-MxyaWjjq.js → xychartDiagram-S5SC5T6Z-1hIHN6Rl.js} +1 -1
- package/package.json +1 -1
- package/src/core/cells/__tests__/cells.test.ts +80 -48
- package/src/core/cells/__tests__/document.test.ts +37 -3
- package/src/core/cells/cells.ts +38 -34
- package/src/core/cells/document.ts +31 -2
- package/src/core/edit-app.tsx +7 -4
- package/src/core/islands/__tests__/bootstrap.test.ts +56 -0
- package/src/core/islands/__tests__/bridge.test.ts +42 -0
- package/src/core/islands/bootstrap.ts +24 -1
- package/src/core/islands/bridge.ts +46 -3
- package/src/core/kernel/__tests__/handlers.test.ts +7 -153
- package/src/core/kernel/handlers.ts +18 -59
- package/src/core/run-app.tsx +1 -7
- package/src/core/session/__tests__/useNotebookSession.hook.test.tsx +0 -6
- package/src/core/session/useNotebookSession.tsx +24 -35
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
/* oxlint-disable typescript/no-explicit-any */
|
|
3
3
|
|
|
4
|
+
import type { NotebookTransport } from "@marimo-team/client";
|
|
4
5
|
import { getWorkerRPC } from "@/core/wasm/rpc";
|
|
5
6
|
import { Deferred } from "@/utils/Deferred";
|
|
6
7
|
import { throwNotImplemented } from "@/utils/functions";
|
|
@@ -8,6 +9,7 @@ import { Logger } from "@/utils/Logger";
|
|
|
8
9
|
import { jsonParseWithSpecialChar } from "@/utils/json/json-parser";
|
|
9
10
|
import { generateUUID } from "@/utils/uuid";
|
|
10
11
|
import { initialNotebookState, notebookAtom } from "../cells/cells";
|
|
12
|
+
import type { NotebookBackend } from "../notebook/replica";
|
|
11
13
|
import type { ClientCommand, NotificationMessage } from "../kernel/messages";
|
|
12
14
|
import type { EditRequests, RunRequests } from "../network/types";
|
|
13
15
|
import { store as defaultStore } from "../state/jotai";
|
|
@@ -70,9 +72,37 @@ export class IslandsPyodideBridge implements RunRequests, EditRequests {
|
|
|
70
72
|
private readonly activeSessionGenerations = new Set<number>();
|
|
71
73
|
private appTransition = Promise.resolve();
|
|
72
74
|
private workerReady = new Deferred<void>();
|
|
75
|
+
/** Where the managed session's host events go while a replica follows them. */
|
|
76
|
+
private stream: Parameters<NotebookTransport>[1] | undefined;
|
|
73
77
|
|
|
74
78
|
public initialized = new Deferred<void>();
|
|
75
79
|
|
|
80
|
+
/**
|
|
81
|
+
* The managed session's host stream, for a replica to follow. Replacing
|
|
82
|
+
* or stopping the session resets the stream, and the next session's
|
|
83
|
+
* snapshot follows.
|
|
84
|
+
*/
|
|
85
|
+
public readonly notebookBackend: NotebookBackend = {
|
|
86
|
+
transport: ({ signal }, handlers) =>
|
|
87
|
+
new Promise((resolve) => {
|
|
88
|
+
this.stream = handlers;
|
|
89
|
+
handlers.open();
|
|
90
|
+
signal.addEventListener(
|
|
91
|
+
"abort",
|
|
92
|
+
() => {
|
|
93
|
+
if (this.stream === handlers) {
|
|
94
|
+
this.stream = undefined;
|
|
95
|
+
}
|
|
96
|
+
resolve();
|
|
97
|
+
},
|
|
98
|
+
{ once: true },
|
|
99
|
+
);
|
|
100
|
+
}),
|
|
101
|
+
notebook: () => {
|
|
102
|
+
throw new Error("Islands do not edit notebooks");
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
76
106
|
public constructor(config: IslandsBridgeConfig = {}) {
|
|
77
107
|
this.store = config.store || defaultStore;
|
|
78
108
|
this.root = config.root || document;
|
|
@@ -119,18 +149,29 @@ export class IslandsPyodideBridge implements RunRequests, EditRequests {
|
|
|
119
149
|
if (!this.activeSessionGenerations.has(sessionGeneration)) {
|
|
120
150
|
return;
|
|
121
151
|
}
|
|
122
|
-
// Islands follow only the notebook's marimo notifications.
|
|
123
152
|
const frame = jsonParseWithSpecialChar(event) as {
|
|
124
153
|
event: string;
|
|
125
154
|
data: { marimo?: NotificationMessage };
|
|
126
155
|
};
|
|
127
|
-
if (frame.event === "marimo"
|
|
128
|
-
|
|
156
|
+
if (frame.event === "marimo") {
|
|
157
|
+
if (frame.data.marimo) {
|
|
158
|
+
this.notificationConsumer?.(frame.data.marimo, sessionGeneration);
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
// The replica follows the managed session's host document.
|
|
163
|
+
if (sessionGeneration === this.session?.sessionGeneration) {
|
|
164
|
+
this.stream?.message(frame);
|
|
129
165
|
}
|
|
130
166
|
},
|
|
131
167
|
);
|
|
132
168
|
}
|
|
133
169
|
|
|
170
|
+
/** The managed session is gone, so its stream starts over. */
|
|
171
|
+
private resetNotebookStream(): void {
|
|
172
|
+
this.stream?.message({ event: "reset", data: {} });
|
|
173
|
+
}
|
|
174
|
+
|
|
134
175
|
public async initializeApps(): Promise<void> {
|
|
135
176
|
await this.enqueueAppTransition(async () => {
|
|
136
177
|
await this.workerReady.promise;
|
|
@@ -185,6 +226,7 @@ export class IslandsPyodideBridge implements RunRequests, EditRequests {
|
|
|
185
226
|
const replacesSession = managesSingleApp && previousSession !== undefined;
|
|
186
227
|
this.activeSessionGenerations.add(request.sessionGeneration);
|
|
187
228
|
if (replacesSession) {
|
|
229
|
+
this.resetNotebookStream();
|
|
188
230
|
this.store.set(notebookAtom, initialNotebookState());
|
|
189
231
|
}
|
|
190
232
|
if (managesSingleApp || !previousSession) {
|
|
@@ -236,6 +278,7 @@ export class IslandsPyodideBridge implements RunRequests, EditRequests {
|
|
|
236
278
|
this.store.set(islandsPendingInitialRunsAtom, null);
|
|
237
279
|
this.session = undefined;
|
|
238
280
|
this.sessionReady = new Deferred<AppSession>();
|
|
281
|
+
this.resetNotebookStream();
|
|
239
282
|
this.store.set(notebookAtom, initialNotebookState());
|
|
240
283
|
});
|
|
241
284
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
import { describe, expect, it, vi } from "vitest";
|
|
3
3
|
import { cellId } from "@/__tests__/branded";
|
|
4
|
-
import {
|
|
4
|
+
import { createCell } from "@/core/cells/types";
|
|
5
|
+
import { buildLayoutState } from "../handlers";
|
|
5
6
|
import type { NotificationMessageData } from "../messages";
|
|
6
7
|
import { queryParamHandlers } from "../queryParamHandlers";
|
|
7
8
|
|
|
@@ -63,152 +64,9 @@ describe("queryParamHandlers", () => {
|
|
|
63
64
|
});
|
|
64
65
|
});
|
|
65
66
|
|
|
66
|
-
describe("buildCellData", () => {
|
|
67
|
-
it("should build cell data from kernel-ready data", () => {
|
|
68
|
-
const kernelReadyData: NotificationMessageData<"kernel-ready"> = {
|
|
69
|
-
cell_ids: [cellId("cell1"), cellId("cell2")],
|
|
70
|
-
codes: ["x = 1", "y = 2"],
|
|
71
|
-
names: ["__", "__"],
|
|
72
|
-
configs: [
|
|
73
|
-
{ disabled: false, hide_code: false },
|
|
74
|
-
{ disabled: false, hide_code: false },
|
|
75
|
-
],
|
|
76
|
-
layout: null,
|
|
77
|
-
resumed: false,
|
|
78
|
-
ui_values: {},
|
|
79
|
-
last_executed_code: {},
|
|
80
|
-
last_execution_time: {},
|
|
81
|
-
app_config: {
|
|
82
|
-
width: "normal",
|
|
83
|
-
app_title: null,
|
|
84
|
-
layout_file: null,
|
|
85
|
-
css_file: null,
|
|
86
|
-
auto_download: [],
|
|
87
|
-
},
|
|
88
|
-
kiosk: false,
|
|
89
|
-
capabilities: {
|
|
90
|
-
terminal: false,
|
|
91
|
-
},
|
|
92
|
-
auto_instantiated: false,
|
|
93
|
-
consumer_capabilities: { edit: true, interact: true },
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const cells = buildCellData(kernelReadyData);
|
|
97
|
-
|
|
98
|
-
expect(cells).toMatchInlineSnapshot(`
|
|
99
|
-
[
|
|
100
|
-
{
|
|
101
|
-
"code": "x = 1",
|
|
102
|
-
"config": {
|
|
103
|
-
"disabled": false,
|
|
104
|
-
"hide_code": false,
|
|
105
|
-
},
|
|
106
|
-
"edited": false,
|
|
107
|
-
"id": "cell1",
|
|
108
|
-
"lastCodeRun": null,
|
|
109
|
-
"lastExecutionTime": null,
|
|
110
|
-
"name": "__",
|
|
111
|
-
"serializedEditorState": null,
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
"code": "y = 2",
|
|
115
|
-
"config": {
|
|
116
|
-
"disabled": false,
|
|
117
|
-
"hide_code": false,
|
|
118
|
-
},
|
|
119
|
-
"edited": false,
|
|
120
|
-
"id": "cell2",
|
|
121
|
-
"lastCodeRun": null,
|
|
122
|
-
"lastExecutionTime": null,
|
|
123
|
-
"name": "__",
|
|
124
|
-
"serializedEditorState": null,
|
|
125
|
-
},
|
|
126
|
-
]
|
|
127
|
-
`);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it("should mark cells as edited when code differs from last executed code", () => {
|
|
131
|
-
const kernelReadyData: NotificationMessageData<"kernel-ready"> = {
|
|
132
|
-
cell_ids: [cellId("cell1"), cellId("cell2")],
|
|
133
|
-
codes: ["x = 1", "y = 3"],
|
|
134
|
-
names: ["__", "__"],
|
|
135
|
-
configs: [
|
|
136
|
-
{ disabled: false, hide_code: false },
|
|
137
|
-
{ disabled: false, hide_code: false },
|
|
138
|
-
],
|
|
139
|
-
layout: null,
|
|
140
|
-
resumed: false,
|
|
141
|
-
ui_values: {},
|
|
142
|
-
last_executed_code: {
|
|
143
|
-
cell1: "x = 1",
|
|
144
|
-
cell2: "y = 2",
|
|
145
|
-
},
|
|
146
|
-
last_execution_time: {
|
|
147
|
-
cell1: 1_234_567_890,
|
|
148
|
-
cell2: 1_234_567_891,
|
|
149
|
-
},
|
|
150
|
-
app_config: {
|
|
151
|
-
width: "normal",
|
|
152
|
-
app_title: null,
|
|
153
|
-
layout_file: null,
|
|
154
|
-
css_file: null,
|
|
155
|
-
auto_download: [],
|
|
156
|
-
},
|
|
157
|
-
kiosk: false,
|
|
158
|
-
capabilities: {
|
|
159
|
-
terminal: false,
|
|
160
|
-
},
|
|
161
|
-
auto_instantiated: false,
|
|
162
|
-
consumer_capabilities: { edit: true, interact: true },
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
const cells = buildCellData(kernelReadyData);
|
|
166
|
-
|
|
167
|
-
expect(cells[0].edited).toBe(false);
|
|
168
|
-
expect(cells[1].edited).toBe(true);
|
|
169
|
-
expect(cells[0].lastCodeRun).toBe("x = 1");
|
|
170
|
-
expect(cells[1].lastCodeRun).toBe("y = 2");
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it("should handle empty cell data", () => {
|
|
174
|
-
const kernelReadyData: NotificationMessageData<"kernel-ready"> = {
|
|
175
|
-
cell_ids: [],
|
|
176
|
-
codes: [],
|
|
177
|
-
names: [],
|
|
178
|
-
configs: [],
|
|
179
|
-
layout: null,
|
|
180
|
-
resumed: false,
|
|
181
|
-
ui_values: {},
|
|
182
|
-
last_executed_code: {},
|
|
183
|
-
last_execution_time: {},
|
|
184
|
-
app_config: {
|
|
185
|
-
width: "normal",
|
|
186
|
-
app_title: null,
|
|
187
|
-
layout_file: null,
|
|
188
|
-
css_file: null,
|
|
189
|
-
auto_download: [],
|
|
190
|
-
},
|
|
191
|
-
kiosk: false,
|
|
192
|
-
capabilities: {
|
|
193
|
-
terminal: false,
|
|
194
|
-
},
|
|
195
|
-
auto_instantiated: false,
|
|
196
|
-
consumer_capabilities: { edit: true, interact: true },
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
const cells = buildCellData(kernelReadyData);
|
|
200
|
-
|
|
201
|
-
expect(cells).toMatchInlineSnapshot("[]");
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
|
|
205
67
|
describe("buildLayoutState", () => {
|
|
206
68
|
it("should build default layout state when no layout is provided", () => {
|
|
207
69
|
const kernelReadyData: NotificationMessageData<"kernel-ready"> = {
|
|
208
|
-
cell_ids: [cellId("cell1")],
|
|
209
|
-
codes: ["x = 1"],
|
|
210
|
-
names: ["__"],
|
|
211
|
-
configs: [{ disabled: false, hide_code: false }],
|
|
212
70
|
layout: null,
|
|
213
71
|
resumed: false,
|
|
214
72
|
ui_values: {},
|
|
@@ -229,7 +87,7 @@ describe("buildLayoutState", () => {
|
|
|
229
87
|
consumer_capabilities: { edit: true, interact: true },
|
|
230
88
|
};
|
|
231
89
|
|
|
232
|
-
const cells =
|
|
90
|
+
const cells = [createCell({ id: cellId("cell1") })];
|
|
233
91
|
const mockSetLayoutData = vi.fn();
|
|
234
92
|
const layoutState = buildLayoutState(
|
|
235
93
|
kernelReadyData,
|
|
@@ -248,13 +106,6 @@ describe("buildLayoutState", () => {
|
|
|
248
106
|
|
|
249
107
|
it("should build layout state with vertical layout", () => {
|
|
250
108
|
const kernelReadyData: NotificationMessageData<"kernel-ready"> = {
|
|
251
|
-
cell_ids: [cellId("cell1"), cellId("cell2")],
|
|
252
|
-
codes: ["x = 1", "y = 2"],
|
|
253
|
-
names: ["__", "__"],
|
|
254
|
-
configs: [
|
|
255
|
-
{ disabled: false, hide_code: false },
|
|
256
|
-
{ disabled: false, hide_code: false },
|
|
257
|
-
],
|
|
258
109
|
layout: {
|
|
259
110
|
type: "vertical",
|
|
260
111
|
data: [],
|
|
@@ -278,7 +129,10 @@ describe("buildLayoutState", () => {
|
|
|
278
129
|
consumer_capabilities: { edit: true, interact: true },
|
|
279
130
|
};
|
|
280
131
|
|
|
281
|
-
const cells =
|
|
132
|
+
const cells = [
|
|
133
|
+
createCell({ id: cellId("cell1") }),
|
|
134
|
+
createCell({ id: cellId("cell2") }),
|
|
135
|
+
];
|
|
282
136
|
const mockSetLayoutData = vi.fn();
|
|
283
137
|
const layoutState = buildLayoutState(
|
|
284
138
|
kernelReadyData,
|
|
@@ -3,7 +3,8 @@ import type { LayoutType } from "@/components/editor/renderers/types";
|
|
|
3
3
|
import { Logger } from "@/utils/Logger";
|
|
4
4
|
import { Objects } from "@/utils/objects";
|
|
5
5
|
import type { UIElementId } from "../cells/ids";
|
|
6
|
-
import
|
|
6
|
+
import type { CellActions } from "../cells/cells";
|
|
7
|
+
import type { CellData } from "../cells/types";
|
|
7
8
|
import { type AppConfig, AppConfigSchema } from "../config/config-schema";
|
|
8
9
|
import { UI_ELEMENT_REGISTRY } from "../dom/uiregistry";
|
|
9
10
|
import { deserializeLayoutState } from "../layout/layout";
|
|
@@ -19,41 +20,6 @@ import type { KernelState } from "./state";
|
|
|
19
20
|
|
|
20
21
|
type KernelReadyData = NotificationMessageData<"kernel-ready">;
|
|
21
22
|
|
|
22
|
-
/**
|
|
23
|
-
* Build cells from kernel-ready data.
|
|
24
|
-
*/
|
|
25
|
-
export function buildCellData(data: KernelReadyData): CellData[] {
|
|
26
|
-
const {
|
|
27
|
-
codes,
|
|
28
|
-
names,
|
|
29
|
-
configs,
|
|
30
|
-
cell_ids,
|
|
31
|
-
last_executed_code,
|
|
32
|
-
last_execution_time,
|
|
33
|
-
} = data;
|
|
34
|
-
|
|
35
|
-
const lastExecutedCode = last_executed_code || {};
|
|
36
|
-
const lastExecutionTime = last_execution_time || {};
|
|
37
|
-
|
|
38
|
-
return codes.map((code, i) => {
|
|
39
|
-
const cellId = cell_ids[i];
|
|
40
|
-
const lastCodeRun = lastExecutedCode[cellId];
|
|
41
|
-
|
|
42
|
-
// A cell is edited if the code has changed since the last time it was run
|
|
43
|
-
const edited = lastCodeRun ? lastCodeRun !== code : false;
|
|
44
|
-
|
|
45
|
-
return createCell({
|
|
46
|
-
id: cellId,
|
|
47
|
-
code,
|
|
48
|
-
edited,
|
|
49
|
-
name: names[i],
|
|
50
|
-
lastCodeRun: lastExecutedCode[cellId] ?? null,
|
|
51
|
-
lastExecutionTime: lastExecutionTime[cellId] ?? null,
|
|
52
|
-
config: configs[i],
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
23
|
/**
|
|
58
24
|
* Build layout state from kernel-ready data.
|
|
59
25
|
*/
|
|
@@ -95,7 +61,10 @@ export function handleKernelReady(
|
|
|
95
61
|
data: KernelReadyData,
|
|
96
62
|
opts: {
|
|
97
63
|
autoInstantiate: boolean;
|
|
98
|
-
|
|
64
|
+
/** The notebook's cells, as the host document provided them. */
|
|
65
|
+
cells: CellData[];
|
|
66
|
+
setLastRun: CellActions["setLastRun"];
|
|
67
|
+
resetCellRuntimes: CellActions["resetCellRuntimes"];
|
|
99
68
|
setLayoutData: (payload: {
|
|
100
69
|
layoutView: LayoutType;
|
|
101
70
|
data: LayoutData;
|
|
@@ -104,17 +73,13 @@ export function handleKernelReady(
|
|
|
104
73
|
setKernelState: (state: KernelState) => void;
|
|
105
74
|
setAppConfig: (config: AppConfig) => void;
|
|
106
75
|
onError: (error: Error) => void;
|
|
107
|
-
/**
|
|
108
|
-
* If provided, these cells will be used instead of the cells from
|
|
109
|
-
* kernel-ready. This allows preserving local edits made before connecting.
|
|
110
|
-
*/
|
|
111
|
-
existingCells?: CellData[];
|
|
112
76
|
},
|
|
113
|
-
) {
|
|
77
|
+
): LayoutState {
|
|
114
78
|
const {
|
|
115
|
-
existingCells,
|
|
116
79
|
autoInstantiate,
|
|
117
|
-
|
|
80
|
+
cells,
|
|
81
|
+
setLastRun,
|
|
82
|
+
resetCellRuntimes,
|
|
118
83
|
setLayoutData,
|
|
119
84
|
setAppConfig,
|
|
120
85
|
setCapabilities,
|
|
@@ -124,15 +89,12 @@ export function handleKernelReady(
|
|
|
124
89
|
const { resumed, ui_values, app_config, capabilities, auto_instantiated } =
|
|
125
90
|
data;
|
|
126
91
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// Set up layout and cells
|
|
92
|
+
resetCellRuntimes();
|
|
93
|
+
setLastRun({
|
|
94
|
+
lastCodeRun: data.last_executed_code ?? {},
|
|
95
|
+
lastExecutionTime: data.last_execution_time ?? {},
|
|
96
|
+
});
|
|
134
97
|
const layoutState = buildLayoutState(data, cells, setLayoutData);
|
|
135
|
-
setCells(cells, layoutState);
|
|
136
98
|
|
|
137
99
|
// Set app config and capabilities
|
|
138
100
|
const parsedAppConfig = AppConfigSchema.safeParse(app_config);
|
|
@@ -145,7 +107,7 @@ export function handleKernelReady(
|
|
|
145
107
|
|
|
146
108
|
// If the kernel was already instantiated server-side (e.g. run mode), we're done
|
|
147
109
|
if (auto_instantiated) {
|
|
148
|
-
return;
|
|
110
|
+
return layoutState;
|
|
149
111
|
}
|
|
150
112
|
|
|
151
113
|
// If resumed, restore UI element values from kernel and we're done
|
|
@@ -156,16 +118,13 @@ export function handleKernelReady(
|
|
|
156
118
|
const uiObjectId: UIElementId = objectId;
|
|
157
119
|
UI_ELEMENT_REGISTRY.set(uiObjectId, value);
|
|
158
120
|
}
|
|
159
|
-
return;
|
|
121
|
+
return layoutState;
|
|
160
122
|
}
|
|
161
123
|
|
|
162
124
|
// If we already have values for some objects, we should
|
|
163
125
|
// send them to the kernel. This may happen after re-connecting
|
|
164
126
|
// to the kernel after the computer wakes from sleep.
|
|
165
127
|
const { objectIds, values } = collectUIElementValues();
|
|
166
|
-
const codesToSend = hasExistingCells
|
|
167
|
-
? Object.fromEntries(existingCells.map((c) => [c.id, c.code]))
|
|
168
|
-
: undefined;
|
|
169
128
|
|
|
170
129
|
// Send instantiate request to kernel
|
|
171
130
|
void getRequestClient()
|
|
@@ -173,7 +132,6 @@ export function handleKernelReady(
|
|
|
173
132
|
objectIds,
|
|
174
133
|
values,
|
|
175
134
|
autoRun: autoInstantiate,
|
|
176
|
-
codes: codesToSend,
|
|
177
135
|
})
|
|
178
136
|
.then(() => {
|
|
179
137
|
setKernelState({ isInstantiated: true, error: null });
|
|
@@ -182,6 +140,7 @@ export function handleKernelReady(
|
|
|
182
140
|
setKernelState({ isInstantiated: false, error: error });
|
|
183
141
|
onError(new Error("Failed to instantiate", { cause: error }));
|
|
184
142
|
});
|
|
143
|
+
return layoutState;
|
|
185
144
|
}
|
|
186
145
|
|
|
187
146
|
export function handleRemoveUIElements(
|
package/src/core/run-app.tsx
CHANGED
|
@@ -11,11 +11,7 @@ import { Spinner } from "@/components/icons/spinner";
|
|
|
11
11
|
import { buttonVariants } from "@/components/ui/button";
|
|
12
12
|
import { cn } from "@/utils/cn";
|
|
13
13
|
import { CellsRenderer } from "../components/editor/renderers/cells-renderer";
|
|
14
|
-
import {
|
|
15
|
-
hasCellsAtom,
|
|
16
|
-
notebookIsRunningAtom,
|
|
17
|
-
useCellActions,
|
|
18
|
-
} from "./cells/cells";
|
|
14
|
+
import { hasCellsAtom, notebookIsRunningAtom } from "./cells/cells";
|
|
19
15
|
import type { AppConfig } from "./config/config-schema";
|
|
20
16
|
import { DocumentProvider } from "./notebook/document";
|
|
21
17
|
import { RuntimeState } from "./kernel/RuntimeState";
|
|
@@ -41,7 +37,6 @@ const canPaintRunAppAtom = atom(
|
|
|
41
37
|
);
|
|
42
38
|
|
|
43
39
|
export const RunApp: React.FC<AppProps> = ({ appConfig, hideHeader }) => {
|
|
44
|
-
const { setCells } = useCellActions();
|
|
45
40
|
const { sendComponentValues } = useRequestClient();
|
|
46
41
|
|
|
47
42
|
// Initialize RuntimeState event-listeners
|
|
@@ -54,7 +49,6 @@ export const RunApp: React.FC<AppProps> = ({ appConfig, hideHeader }) => {
|
|
|
54
49
|
|
|
55
50
|
const { connection, reconnect, documentModel } = useNotebookSession({
|
|
56
51
|
autoInstantiate: true,
|
|
57
|
-
setCells: setCells,
|
|
58
52
|
sessionId: getSessionId(),
|
|
59
53
|
});
|
|
60
54
|
|
|
@@ -159,7 +159,6 @@ function renderConnectionHook(
|
|
|
159
159
|
useNotebookSession({
|
|
160
160
|
sessionId: SESSION_ID,
|
|
161
161
|
autoInstantiate: false,
|
|
162
|
-
setCells: vi.fn(),
|
|
163
162
|
}),
|
|
164
163
|
{ wrapper, reactStrictMode },
|
|
165
164
|
);
|
|
@@ -334,10 +333,6 @@ it.each(["kernel-ready", "reconnected"])(
|
|
|
334
333
|
act(() =>
|
|
335
334
|
conn.send({
|
|
336
335
|
op,
|
|
337
|
-
cell_ids: [],
|
|
338
|
-
codes: [],
|
|
339
|
-
names: [],
|
|
340
|
-
configs: [],
|
|
341
336
|
layout: null,
|
|
342
337
|
resumed: true,
|
|
343
338
|
ui_values: {},
|
|
@@ -367,7 +362,6 @@ describe("connection notice", () => {
|
|
|
367
362
|
const { reconnect } = useNotebookSession({
|
|
368
363
|
sessionId: SESSION_ID,
|
|
369
364
|
autoInstantiate: false,
|
|
370
|
-
setCells: vi.fn(),
|
|
371
365
|
});
|
|
372
366
|
return (
|
|
373
367
|
<ConnectionNotice
|
|
@@ -5,7 +5,8 @@ import { useAtom, useAtomValue, useSetAtom, useStore } from "jotai";
|
|
|
5
5
|
import { useEffect, useLayoutEffect, useMemo } from "react";
|
|
6
6
|
import { useErrorBoundary } from "react-error-boundary";
|
|
7
7
|
import { toast } from "@/components/ui/use-toast";
|
|
8
|
-
import { getNotebook, useCellActions } from "@/core/cells/cells";
|
|
8
|
+
import { getNotebook, notebookAtom, useCellActions } from "@/core/cells/cells";
|
|
9
|
+
import { followDocument } from "@/core/cells/document";
|
|
9
10
|
import { AUTOCOMPLETER } from "@/core/codemirror/completion/Autocompleter";
|
|
10
11
|
import { createDocumentModel } from "@/core/notebook/document";
|
|
11
12
|
import type { NotificationPayload } from "@/core/kernel/messages";
|
|
@@ -22,14 +23,13 @@ import { Logger } from "@/utils/Logger";
|
|
|
22
23
|
import { reloadSafe } from "@/utils/reload-safe";
|
|
23
24
|
import { useAlertActions } from "../alerts/state";
|
|
24
25
|
import { cacheInfoAtom } from "../cache/requests";
|
|
25
|
-
import { SCRATCH_CELL_ID } from "../cells/ids";
|
|
26
26
|
import { useRunsActions } from "../cells/runs";
|
|
27
27
|
import { focusAndScrollCellOutputIntoView } from "../cells/scrollCellIntoView";
|
|
28
28
|
import {
|
|
29
29
|
resyncBreakpoints,
|
|
30
30
|
setActiveLine,
|
|
31
31
|
} from "../codemirror/cells/debugger-state";
|
|
32
|
-
import
|
|
32
|
+
import { notebookCells } from "../cells/utils";
|
|
33
33
|
import { capabilitiesAtom } from "../config/capabilities";
|
|
34
34
|
import { connectionTransportTypeAtom, useSetAppConfig } from "../config/config";
|
|
35
35
|
import { useDataSourceActions } from "../datasets/data-source-connections";
|
|
@@ -75,30 +75,23 @@ import type { VariableName } from "../variables/types";
|
|
|
75
75
|
import { activeDocumentModelAtom } from "./active-notebook";
|
|
76
76
|
import { WebSocketClosedReason, ConnectionState } from "./types";
|
|
77
77
|
|
|
78
|
-
const SUPPORTS_LAZY_KERNELS = true;
|
|
79
|
-
|
|
80
|
-
function getExistingCells(): CellData[] | undefined {
|
|
81
|
-
if (!SUPPORTS_LAZY_KERNELS) {
|
|
82
|
-
return undefined;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Remove scratch pad
|
|
86
|
-
return Object.values(getNotebook().cellData).filter(
|
|
87
|
-
(cell) => cell.id !== SCRATCH_CELL_ID,
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
78
|
/** Coordinate session startup and apply notifications to the editor. */
|
|
92
79
|
export function useNotebookSession(opts: {
|
|
93
80
|
sessionId: SessionId;
|
|
94
81
|
autoInstantiate: boolean;
|
|
95
|
-
|
|
82
|
+
/** Called with the layout the kernel reported, after its cells are ready. */
|
|
83
|
+
onKernelReady?: (layout: LayoutState) => void;
|
|
96
84
|
}) {
|
|
97
|
-
const { autoInstantiate, sessionId,
|
|
85
|
+
const { autoInstantiate, sessionId, onKernelReady } = opts;
|
|
98
86
|
const { showBoundary } = useErrorBoundary();
|
|
87
|
+
const store = useStore();
|
|
99
88
|
|
|
100
|
-
const {
|
|
101
|
-
|
|
89
|
+
const {
|
|
90
|
+
handleCellMessage,
|
|
91
|
+
reconcileDocument,
|
|
92
|
+
setLastRun,
|
|
93
|
+
resetCellRuntimes,
|
|
94
|
+
} = useCellActions();
|
|
102
95
|
const { addCellNotification } = useRunsActions();
|
|
103
96
|
const setKernelState = useSetAtom(kernelStateAtom);
|
|
104
97
|
const setInitialRunCompleted = useSetAtom(initialRunCompletedAtom);
|
|
@@ -153,18 +146,18 @@ export function useNotebookSession(opts: {
|
|
|
153
146
|
setInitialRunCompleted(
|
|
154
147
|
Boolean(msg.data.resumed || msg.data.auto_instantiated),
|
|
155
148
|
);
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
handleKernelReady(msg.data, {
|
|
149
|
+
const layout = handleKernelReady(msg.data, {
|
|
159
150
|
autoInstantiate,
|
|
160
|
-
|
|
151
|
+
cells: notebookCells(store.get(notebookAtom)),
|
|
152
|
+
setLastRun,
|
|
153
|
+
resetCellRuntimes,
|
|
161
154
|
setLayoutData,
|
|
162
155
|
setAppConfig,
|
|
163
156
|
setCapabilities,
|
|
164
157
|
setKernelState,
|
|
165
158
|
onError: showBoundary,
|
|
166
|
-
existingCells,
|
|
167
159
|
});
|
|
160
|
+
onKernelReady?.(layout);
|
|
168
161
|
if (!msg.data.resumed) {
|
|
169
162
|
// A freshly started kernel may expose a different environment
|
|
170
163
|
// (e.g. new env vars); re-run discovery instead of reusing stale
|
|
@@ -393,7 +386,6 @@ export function useNotebookSession(opts: {
|
|
|
393
386
|
}
|
|
394
387
|
};
|
|
395
388
|
|
|
396
|
-
const store = useStore();
|
|
397
389
|
const onMessageError = (error: unknown) => {
|
|
398
390
|
toast({
|
|
399
391
|
title: "Failed to handle message",
|
|
@@ -443,16 +435,13 @@ export function useNotebookSession(opts: {
|
|
|
443
435
|
const controller = new AbortController();
|
|
444
436
|
documentModel.start({ signal: controller.signal });
|
|
445
437
|
store.set(activeDocumentModelAtom, documentModel);
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
};
|
|
452
|
-
const unsubscribe = store.sub(documentModel.documentAtom, follow);
|
|
453
|
-
follow();
|
|
438
|
+
followDocument({
|
|
439
|
+
store,
|
|
440
|
+
model: documentModel,
|
|
441
|
+
reconcile: reconcileDocument,
|
|
442
|
+
signal: controller.signal,
|
|
443
|
+
});
|
|
454
444
|
return () => {
|
|
455
|
-
unsubscribe();
|
|
456
445
|
controller.abort();
|
|
457
446
|
if (store.get(activeDocumentModelAtom) === documentModel) {
|
|
458
447
|
store.set(activeDocumentModelAtom, null);
|