@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.
Files changed (86) hide show
  1. package/dist/{ConnectedDataExplorerComponent-BbSXV-Xa.js → ConnectedDataExplorerComponent-Le_OBfmt.js} +9 -9
  2. package/dist/{ErrorBoundary-NE86qEWZ.js → ErrorBoundary-wT3f3iJ1.js} +8 -8
  3. package/dist/{abnfDiagram-VCTEODGH-D7n07nXf.js → abnfDiagram-VCTEODGH-Tf1ghyQQ.js} +2 -2
  4. package/dist/{any-language-editor-3E24SnCV.js → any-language-editor-B92WMzoC.js} +2 -2
  5. package/dist/{architectureDiagram-5GKGNRK7-D6CjRib5.js → architectureDiagram-5GKGNRK7-xlnbTG0T.js} +1 -1
  6. package/dist/{chat-ui-CC7GekpG.js → chat-ui-CuORr6fL.js} +70 -70
  7. package/dist/{check-SpOMoCLP.js → check-C9Mp9eCK.js} +1 -1
  8. package/dist/{chunk-IMKFNOWR-6dtPz7uS.js → chunk-IMKFNOWR-Cmd4a5jy.js} +1 -1
  9. package/dist/{chunk-SHT3W25Y-BwnFNKDA.js → chunk-SHT3W25Y-mSNp-WpO.js} +1 -1
  10. package/dist/{chunk-SVP7TREG-D2eV4vBJ.js → chunk-SVP7TREG-CWC49D8X.js} +1 -1
  11. package/dist/{chunk-TICWLB2K-BDs4-_-r.js → chunk-TICWLB2K-De9FINtG.js} +1 -1
  12. package/dist/{classDiagram-ZZMXUADV-DCJnf_8v.js → classDiagram-ZZMXUADV-DweTkYdp.js} +2 -2
  13. package/dist/{classDiagram-v2-VYDZK3BY-BOmSzPBD.js → classDiagram-v2-VYDZK3BY-A6MR2TiE.js} +2 -2
  14. package/dist/{code-visibility-Bzvc6ajl.js → code-visibility-ZReaeMaF.js} +5 -5
  15. package/dist/{common-Ciw4zkh2.js → common-DCevHg1m.js} +231 -231
  16. package/dist/{copy-icon-C1trdvVt.js → copy-icon-De584pZ4.js} +2 -2
  17. package/dist/{cynefinDiagram-5FMLGOSQ-Bln_Soky.js → cynefinDiagram-5FMLGOSQ-BSvo5le-.js} +1 -1
  18. package/dist/{dagre-GXQ25YYZ-D-nf7WzW.js → dagre-GXQ25YYZ-tXRQslO6.js} +1 -1
  19. package/dist/{data-grid-overlay-editor-AQREmxI7.js → data-grid-overlay-editor-CTC-bJus.js} +1 -1
  20. package/dist/{diagram-S7CK7UJ4-PsgSMkY2.js → diagram-S7CK7UJ4-r3t5VtwI.js} +1 -1
  21. package/dist/{diagram-UQ7AKVKN-DfwfUuSw.js → diagram-UQ7AKVKN-qgbC8YnT.js} +1 -1
  22. package/dist/{diagram-VX7I27RA-C9_e-oMc.js → diagram-VX7I27RA-CiHxEwtp.js} +1 -1
  23. package/dist/{diagram-Z3DM3KII-CWLa5tuY.js → diagram-Z3DM3KII-D95u15Ev.js} +1 -1
  24. package/dist/{dialog-DgeqyrEZ.js → dialog-DVRfQuhD.js} +24 -24
  25. package/dist/{dropdown-menu-B6ftyzOg.js → dropdown-menu-DCMbbQ35.js} +691 -691
  26. package/dist/{ebnfDiagram-PWID7BFC-B1QRM58v.js → ebnfDiagram-PWID7BFC-P7e4bryK.js} +2 -2
  27. package/dist/{erDiagram-RLTQ6QDP-DJK_FqvW.js → erDiagram-RLTQ6QDP-CUHZcmRj.js} +1 -1
  28. package/dist/{error-banner-D40pmfQQ.js → error-banner-DxJ3s8YO.js} +57 -57
  29. package/dist/{error-entries-DgK3wUJR.js → error-entries-CmWMEsY_.js} +984 -961
  30. package/dist/{feedback-button-8a6SETmT.js → feedback-button-DwXkmewo.js} +29 -29
  31. package/dist/{flowDiagram-HODETNUW-Uxc25XSJ.js → flowDiagram-HODETNUW-BGZ95AzI.js} +2 -2
  32. package/dist/{formats-DQCyPd5Z.js → formats-DI7HEVv7.js} +1 -1
  33. package/dist/{glide-data-editor-B70oke_v.js → glide-data-editor-BlmcP-lw.js} +10 -10
  34. package/dist/{glide-utils-Dz_F4pAY.js → glide-utils-D98laidb.js} +2 -2
  35. package/dist/{globals-Cxd5hNlI.js → globals-BdRitZ4p.js} +3 -3
  36. package/dist/{html-to-image-D4yG5Ru0.js → html-to-image-bpiQh7ND.js} +55 -55
  37. package/dist/{infoDiagram-27XIBGKW-DmjxqwL0.js → infoDiagram-27XIBGKW-BF0YX70D.js} +1 -1
  38. package/dist/{ishikawaDiagram-5VMMS53U-NgCLHqQi.js → ishikawaDiagram-5VMMS53U-_itsAAn-.js} +1 -1
  39. package/dist/{kanban-definition-UXKFOSKX-CZaG-iam.js → kanban-definition-UXKFOSKX-VNW2Beqh.js} +1 -1
  40. package/dist/{label-D9_-zSg-.js → label-Qvw1E97W.js} +1 -1
  41. package/dist/main.js +8159 -6508
  42. package/dist/{mermaid-CSvw7c71.js → mermaid-CZ6FrClS.js} +35 -35
  43. package/dist/{mindmap-definition-YA3MSWOX-DJYQBDH_.js → mindmap-definition-YA3MSWOX-DR4ripvd.js} +1 -1
  44. package/dist/{number-overlay-editor-ChK-SKUk.js → number-overlay-editor-DQG-hFHT.js} +1 -1
  45. package/dist/{pegDiagram-XKGWAZYB-CHKO_dAe.js → pegDiagram-XKGWAZYB-CFr3XlGl.js} +2 -2
  46. package/dist/{pieDiagram-E7YTZNPT-DVevbMAo.js → pieDiagram-E7YTZNPT-j91XYSLY.js} +1 -1
  47. package/dist/{process-output-DxvBAeL3.js → process-output-BzloOI7F.js} +4 -4
  48. package/dist/{railroadDiagram-O6MQD6OU-Detj1kcB.js → railroadDiagram-O6MQD6OU-Cd47ZV9E.js} +2 -2
  49. package/dist/{requirementDiagram-BXWQKSXE-BLFD0T_Z.js → requirementDiagram-BXWQKSXE-eJH6gLtX.js} +1 -1
  50. package/dist/{reveal-component-CXMXeDZD.js → reveal-component-D3r3vSTK.js} +9 -9
  51. package/dist/{select-CkF_GBxO.js → select-2KOiReNY.js} +324 -324
  52. package/dist/{sequenceDiagram-WJ2MYXX4-BZBgatQV.js → sequenceDiagram-WJ2MYXX4-DdfFEIrm.js} +1 -1
  53. package/dist/{spec-Mnkn3XEr.js → spec-h69nC3SI.js} +4 -4
  54. package/dist/{stateDiagram-D77RDMKH-4BZW9-6_.js → stateDiagram-D77RDMKH-DkjDvip8.js} +2 -2
  55. package/dist/{stateDiagram-v2-MP3YSRHH-C4sKC8Iq.js → stateDiagram-v2-MP3YSRHH-DBHACnOo.js} +2 -2
  56. package/dist/{swimlanes-42K2YHIH-SL5yeGe2.js → swimlanes-42K2YHIH-ogImlOmp.js} +1 -1
  57. package/dist/{swimlanesDiagram-VR7AAH4N-DqPmAYUv.js → swimlanesDiagram-VR7AAH4N-D6x-zsci.js} +2 -2
  58. package/dist/{timeline-definition-24CTP7MA-BfXUMWkq.js → timeline-definition-24CTP7MA-DQpLtsHD.js} +1 -1
  59. package/dist/{toDate-TMubKIVL.js → toDate-CDzoofJo.js} +2 -2
  60. package/dist/{toast-Btbve3Lm.js → toast-B5t9o6s_.js} +47 -47
  61. package/dist/{tooltip-CJ4Rqasp.js → tooltip-s4zZxwKe.js} +67 -67
  62. package/dist/{use-restore-focus-CmGwRNId.js → use-restore-focus-BgDTd2tc.js} +1 -1
  63. package/dist/{useAsyncData-DjMGFzFy.js → useAsyncData-Bn8SxUqM.js} +1 -1
  64. package/dist/{useDateFormatter-eCL_SdeG.js → useDateFormatter-D3Tkfzej.js} +2 -2
  65. package/dist/{useDeepCompareMemoize-BQR5TJot.js → useDeepCompareMemoize-CC2tnPCk.js} +1 -1
  66. package/dist/{useLifecycle-C_GvKWbQ.js → useLifecycle-BJlxHqWa.js} +2 -2
  67. package/dist/{useTheme-Bns2gJZ6.js → useTheme-DMfQYmlG.js} +85 -85
  68. package/dist/{vega-component-BsVptuhz.js → vega-component-CovWHi60.js} +7 -7
  69. package/dist/{vennDiagram-4TSXK5OY-CH65znGa.js → vennDiagram-4TSXK5OY-BD-SL_vq.js} +1 -1
  70. package/dist/{wardleyDiagram-VM6X3IG4-z9pWKb2_.js → wardleyDiagram-VM6X3IG4-BDgJV4ah.js} +1 -1
  71. package/dist/{xychartDiagram-S5SC5T6Z-MxyaWjjq.js → xychartDiagram-S5SC5T6Z-1hIHN6Rl.js} +1 -1
  72. package/package.json +1 -1
  73. package/src/core/cells/__tests__/cells.test.ts +80 -48
  74. package/src/core/cells/__tests__/document.test.ts +37 -3
  75. package/src/core/cells/cells.ts +38 -34
  76. package/src/core/cells/document.ts +31 -2
  77. package/src/core/edit-app.tsx +7 -4
  78. package/src/core/islands/__tests__/bootstrap.test.ts +56 -0
  79. package/src/core/islands/__tests__/bridge.test.ts +42 -0
  80. package/src/core/islands/bootstrap.ts +24 -1
  81. package/src/core/islands/bridge.ts +46 -3
  82. package/src/core/kernel/__tests__/handlers.test.ts +7 -153
  83. package/src/core/kernel/handlers.ts +18 -59
  84. package/src/core/run-app.tsx +1 -7
  85. package/src/core/session/__tests__/useNotebookSession.hook.test.tsx +0 -6
  86. 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" && frame.data.marimo) {
128
- this.notificationConsumer?.(frame.data.marimo, sessionGeneration);
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 { buildCellData, buildLayoutState } from "../handlers";
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 = buildCellData(kernelReadyData);
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 = buildCellData(kernelReadyData);
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 { type CellData, createCell } from "../cells/types";
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
- setCells: (cells: CellData[], layout: LayoutState) => void;
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
- setCells,
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
- // Use existing cells if provided (local pre-connect edits), otherwise build from kernel-ready
128
- // If the kernel was resumed, we don't want to use the existing cells because they may be stale.
129
- const hasExistingCells = existingCells && existingCells.length > 0;
130
- const cells =
131
- hasExistingCells && !resumed ? existingCells : buildCellData(data);
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(
@@ -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 type { CellData } from "../cells/types";
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
- setCells: (cells: CellData[], layout: LayoutState) => void;
82
+ /** Called with the layout the kernel reported, after its cells are ready. */
83
+ onKernelReady?: (layout: LayoutState) => void;
96
84
  }) {
97
- const { autoInstantiate, sessionId, setCells } = opts;
85
+ const { autoInstantiate, sessionId, onKernelReady } = opts;
98
86
  const { showBoundary } = useErrorBoundary();
87
+ const store = useStore();
99
88
 
100
- const { handleCellMessage } = useCellActions();
101
- const { reconcileDocument } = useCellActions({ skipMiddleware: true });
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 existingCells = getExistingCells();
157
-
158
- handleKernelReady(msg.data, {
149
+ const layout = handleKernelReady(msg.data, {
159
150
  autoInstantiate,
160
- setCells,
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
- const follow = () => {
447
- const document = store.get(documentModel.documentAtom);
448
- if (document !== null) {
449
- reconcileDocument(document);
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);